Compare commits

...

15 Commits

Author SHA1 Message Date
Can Tecim 5b75e53859
Merge f70a9e0569 into 3d824d4c0f 2026-04-24 12:09:23 -04:00
Brian 3d824d4c0f
feat(installer): channel-based version resolution + interactive channel management (#2305)
* feat(installer): channel-based version resolution for external modules

Adds stable/next/pinned channel resolution so external/community modules
install at released git tags by default instead of tracking main HEAD.
Manifest now records channel, resolved version, and SHA per module for
reproducible installs.

CLI flags: --channel, --all-stable, --all-next, --next=CODE (repeatable),
--pin CODE=TAG (repeatable). Precedence: pin > next > channel > registry
default > stable. --yes accepts patch/minor upgrades but refuses majors.

Interactive "Ready to install (all stable)?" gate with a per-module
picker (stable/next/pin) when declined. Re-install prompts classify tag
diffs as patch/minor/major with semver-class-dependent defaults.
Legacy version:null manifests get a one-time migration prompt.

Custom modules gain an optional @<ref> URL suffix for pinning (https,
ssh, /tree/<ref>/subdir forms supported; local paths rejected).
Community modules honor --next/--pin overrides with a curator-bypass
warning; default path still enforces the approved SHA.

Quick-update now reads the manifest's recorded channel per module so
pinned installs don't silently roll forward.

* feat(installer): interactive channel switch, upgrade refusal, unified docs

Builds on the channel-resolution foundation. The installer now lets users
flip a module between stable, next, and pinned after install — either
interactively via a "Review channel assignments?" gate, or by flag. Quick
and modify re-installs classify stable upgrades; under non-interactive
flows, patches and minors apply automatically but majors are refused with
a pointer to --pin.

Fallback behavior for GitHub rate-limit / network failures is now cache-
aware: re-installs reuse the recorded ref silently; fresh installs abort
with actionable guidance (set GITHUB_TOKEN or use --next/--pin). Bundled
modules (core, bmm) warn when targeted by --pin or --next so users aren't
left wondering why the flag had no effect.

Install summary labels no longer mangle "main" into "vmain"; next-channel
entries render as "main @ <short-sha>" instead. Bundled modules are now
correctly skipped from all channel prompts and tag-API lookups.

Docs consolidated into a single how-to. install-bmad.md now covers the
interactive flow, the channel model (stable/next/pinned plus the npm
dist-tag axis for core/bmm), the re-install upgrade prompts, the full
flag reference, copy-paste recipes, and troubleshooting. The old
non-interactive-installation.md is reduced to a redirect stub.

* fix(installer): review fixes + unit tests for channel resolution

- ui.js: import parseGitHubRepo; fixes ReferenceError in the
  interactive channel picker's stable-tag pre-resolve path.
- community-manager: pinned modules now fetch+checkout the pin tag
  on cache refresh instead of resetting to origin/HEAD (was silently
  drifting to main on re-install).
- channel-plan: parseChannelOptions returns acceptBypass so --yes
  auto-confirms the curator-bypass prompt; headless --next/--pin
  installs of community modules no longer hang.
- community-manager: simplify recordedVersion (dead ternary branch).
- custom-module-manager: drop "or sha" from the @<ref> comment
  (git clone --branch rejects raw SHAs); update-path fetches
  origin <ref> so /tree/<branch>/ URLs work too.
- install-bmad.md: rename "Headless / CI installs" to "Headless CI
  installs" so the stub's #headless-ci-installs anchor resolves.
- test/test-installer-channels.js: 83 unit tests for channel-plan
  and channel-resolver pure modules; wired into npm test as
  test:channels.

* fix(installer): address CodeRabbit review findings

- ui.js: skip stable-channel upgrade classification when the user has
  already declared intent via --pin/--next=/--channel or the review
  gate. Prevents the decline / major-refused / fetch-error branches
  from silently overwriting an explicit pin with prev.version.
- external-manager.js: short-circuit cloneExternalModule when the
  requested plan matches an existing in-process resolution and the
  cache is valid. Avoids redundant resolveChannel() + git fetch on
  every same-plan lookup in a single install.
- installer.js: fall back to CommunityModuleManager.getResolution()
  when no external resolution exists, so community module result
  rows carry newChannel/newSha instead of null under --next/--pin.
- installer.js: don't label a module as "no change" when its version
  string is 'main'/'HEAD' — the SHA may have moved and preVersions
  doesn't track the prior SHA. Show "(refreshed)" instead.
- official-modules.js: match versionInfo.version to the manifest's
  cloneRef || (hasGitClone ? 'main' : version) expression so summary
  lines report the cloned ref for git-backed custom installs.
- install-bmad.md: clarify that sha is only written for git-backed
  modules and that rerunning the same --modules on another machine
  does not reproduce stable-channel installs — convert recorded tags
  into explicit --pin flags for cross-machine reproducibility.
2026-04-24 08:20:30 -05:00
Murat K Ozcan 2395b0e2ed
fix: bmad tea instal version (#2298)
* fix: bmad tea instal version

* fix: addressed review comments
2026-04-22 11:03:20 -05:00
Brian 914c4edd6b
fix(installer): resolve external-module agents from cache during manifest write (#2295)
External official modules (bmb, cis, gds, tea, wds) are cloned to
~/.bmad/cache/external-modules/<name>/ and never copied into src/modules/,
so collectAgentsFromModuleYaml silently skipped them and their agents
never reached config.toml. Swap the hardcoded src/modules lookup for a
resolveInstalledModuleYaml() helper that also searches the external cache
(handling src/, skills/, nested, and root layouts) and warns instead of
silently skipping when a module.yaml can't be found.
2026-04-21 22:51:04 -05:00
miendinh 16c9976d7e
docs(vi-vn): sync and update Vietnamese documentation (#2291)
Co-authored-by: miendinh <miendinh@users.noreply.github.com>
2026-04-21 21:31:53 -05:00
Brian 87292cd86a
feat(skills): wire on_complete into terminal steps; add full customize.toml comments (#2290)
All 16 bare workflow customize.toml files now have the same thorough comment
block as the well-documented ones, with skill-specific on_complete descriptions
that name the exact terminal step and exit condition.

on_complete is now executed at the true end of each workflow's terminal step
rather than lazily referenced in SKILL.md:

- Linear workflows: ## On Complete block appended to the final step file
  (create-prd step-12, create-ux-design step-14, create-architecture step-08,
  generate-project-context step-03, check-implementation-readiness step-06,
  epics-and-stories step-04, all three research step-06 files, prfaq verdict,
  document-project both sub-workflow instruction files)
- Multi-path workflows: on_complete inline on each true exit path only
  (edit-prd fires on [S] Summary and [X] Exit, not on [V] Validate or [E] Edit;
  validate-prd fires on [X] Exit only, not on [R], [E], or [F])
- Inline XML workflows: <action> tag at the close of the final step
  (correct-course step-6, create-story step-6, retrospective step-12,
  qa-generate-e2e-tests appended to SKILL.md)
2026-04-20 22:53:23 -05:00
Brian b63086f22e
feat(core-skills): add bmad-customize — guided authoring for _bmad/custom overrides (#2289)
* feat(core-skills): add bmad-customize for authoring _bmad/custom overrides

A conversational guide skill that helps users author or update TOML overrides
in _bmad/custom/ for customizable BMad agents and workflows. Covers per-skill
agent and workflow surfaces; central config is out of scope for v1.

- SKILL.md: six-step flow (intent, discover, route, compose, team-vs-user,
  show-confirm-write-verify) with baked-in agent-vs-workflow routing heuristic
  and a template-swap subroutine
- scripts/list_customizable_skills.py: stdlib-only scanner that enumerates
  customizable skills across standard IDE install paths, reports surface type
  and override status, PEP 723, 10 unit tests
- Reuses _bmad/scripts/resolve_customization.py for post-write verification
- Registered in core-skills/module-help.csv with menu code BC

* refactor(bmad-customize): apply QA pass (top 3 recommendations)

Applies the three highest-payoff themes from the quality analysis:

- Labeling + completion contracts: rename ## Purpose to ## Overview,
  add domain framing (what customization means in BMad, typical user
  arrival shapes), add an explicit Completion block with testable
  conditions for "skill run is done"
- Hostile-environment robustness: add On-Activation preflight that
  classifies no-BMad / BMad-without-resolver / full-install states,
  instruct Step 2 to surface scanner errors[] and scanned_roots on
  empty results, add resolver-missing fallback to Step 6.4, add a
  re-enter-Step-4 recovery loop when verify shows the override didn't
  take effect
- Returning-user and iteration experience: add "Audit / iterate"
  intent class in Step 1, lead discovery with already-overridden
  skills for that intent, read existing overrides in Step 3 before
  composing, frame Step 4 as additive-on-top rather than fresh
  authoring, give Cross-cutting intent an explicit Step 3 branch
  that walks agent-vs-workflow with the user

Resolves 12 of 18 observations from the quality report. Lint clean
(scan-path-standards and scan-scripts both 0 findings). Unit tests
still 10/10.

* refactor(bmad-customize): derive skills root from install location

Previously the scanner hardcoded a list of IDE skill directories
(.claude/skills, .cursor/skills, .cline/skills, .continue/skills) and
scanned them relative to the project root. That was wrong: skills can
be installed either project-local or user-global, the IDE determines
the convention, and the set of valid locations is open-ended.

The scanner now derives its primary skills root from __file__ — the
running skill's own install directory is the authoritative location
for finding siblings. --skills-root overrides the default; --extra-root
(repeatable) adds additional locations for the rare mixed-install case.

Changes:
- list_customizable_skills.py: remove SKILL_ROOTS constant, add
  default_skills_root() derived from __file__, rename scan_project
  to scan_skills(skills_roots, project_root), add --skills-root and
  --extra-root flags, de-dupe skills when the same name appears in
  multiple roots (first wins)
- SKILL.md: update Step 2 to describe the scanner's derive-from-install
  behavior and when to use --extra-root; drop the hardcoded IDE path
  list from Notes
- tests: refactor setUp to place skills under a generic skills root
  (not .claude/skills), add 3 new tests for multiple-roots merge,
  duplicate-name precedence, and missing-root error reporting

* docs(customization): point users at bmad-customize as the guided path

Surface the new bmad-customize skill across the three customization
docs so users know they don't need to hand-author TOML to benefit
from the surface:

- customize-bmad.md: prominent tip at the top introducing the skill
  as the guided authoring helper; updated the "Need to see what's
  customizable?" troubleshooting tip to recommend the skill first
- expand-bmad-for-your-org.md: tip under prereqs noting every recipe
  can be applied via the skill, with the recipes remaining the
  reference for what to override
- named-agents.md: short paragraph in the customization section and a
  link entry under the references list

Hand-authoring still works the same way; the skill is additive.
Central-config overrides are flagged as the current exception.

* docs(bmad-customize): steer users at bmad-builder instead of 'forking'

* fix(bmad-customize): reword description to pass file-ref validator

* refactor(bmad-customize): tighten description and expand module-help entry

- SKILL.md description: drop the catch-all 'or asks how to change the
  behavior of a specific BMad skill' trigger clause that would fire in
  casual discussion; keep the four explicit phrase triggers.
- module-help.csv: rewrite the description so bmad-help has real
  routing material — names the concrete capabilities (persistent
  facts, template swaps, activation hooks, menus), the scope routing,
  and the value prop (no TOML hand-authoring). Matches the 'Use
  when...' pattern other Core entries use.

* fix(module-help): quote bmad-customize description field that contains commas

* fix(bmad-customize): address PR #2289 review findings

- SKILL.md preflight: load root config from _bmad/config.toml and
  config.user.toml (not .yaml) — the installer emits TOML; the YAML
  references would have made the skill silently miss real user config
- SKILL.md resolver fallback (Step 6.4): read all three merge layers
  when present (base / team / user) and describe the merge in
  base → team → user order; the prior wording could describe the wrong
  effective merge when the user wrote .user.toml on top of an existing
  team .toml
- SKILL.md: replace bare 'docs/how-to/customize-bmad.md' references
  (3 locations) with the public docs URL so users installing the skill
  aren't pointed at a path they don't have locally
- list_customizable_skills.py: catch UnicodeDecodeError in
  read_frontmatter_description so a non-UTF-8 SKILL.md can't abort
  the whole scan
- list_customizable_skills.py: clarify exit-code contract in the
  module docstring — errors[] is non-fatal by design, exit 2 is
  reserved for invocation errors
- customize-bmad.md: tighten the tip to scope bmad-customize to the
  per-skill surface; central-config is out of scope v1
- expand-bmad-for-your-org.md: same scoping — Recipes 1-4 can be
  applied by the skill; Recipe 5 (central config) stays hand-authored

* fix(bmad-customize): markdownlint MD034 and validate-file-refs

- Wrap the three docs.bmad-method.org references as
  [text](url) markdown links instead of bare URLs (MD034)
- Drop the {project-root}/ prefix on line 41's config.toml
  references. validate-file-refs strips the template prefix and
  tries to resolve 'config.toml' as 'src/config.toml'; sibling
  skills (party-mode, retrospective, advanced-elicitation) all
  reference '_bmad/config.toml' bare and pass CI — match that
  pattern. The '(root level under {project-root}, installer-owned)'
  parenthetical preserves the disambiguation.

* refactor(bmad-customize): cut token-wasting prose from SKILL.md

Down from 175 lines to 110. Removed:
- 'What customization means in BMad' architecture backgrounder — the
  LLM reads the live customize.toml in Step 3; doesn't need the lore
- 'Desired Outcomes' section — retrospective narration of what the
  6 steps already instruct
- 'Role' section — fluff; the flow itself defines the role
- 'Notes' section — sparse-override rule already in Step 4, IDE-path
  note is commentary, docs link duplicates the out-of-scope section
- 'The scanner derives its skills directory from...' and 'returns JSON
  with...' — commentary the LLM doesn't need; it runs the script and
  sees the output
- 'that file IS the schema' and similar editorial asides throughout
- Explanatory clauses like 'silently drifts on every release' and
  'trust the user's domain knowledge'

Kept everything that's load-bearing: preflight conditionals, intent
classification, routing heuristic, merge semantics, template-swap
subroutine, team-vs-user defaults, verify fallback and recovery loop,
completion conditions, out-of-scope list.
2026-04-20 22:14:54 -05:00
Brian ffdd9bc69e
feat(skills): add TOML workflow customization to 17 bmm-skills (#2287)
* feat(skills): add TOML workflow customization to 17 bmm-skills

Flattens each skill's workflow.md into SKILL.md and adds a customize.toml
surface with a 6-step activation block (resolve_customization, prepend,
persistent_facts, config, greet, append). Core-skills and developer
execution skills (dev-story, code-review, sprint-planning, sprint-status,
quick-dev, checkpoint-preview) are intentionally excluded.

Customized: document-project, prfaq, domain/market/technical-research,
create-prd, create-ux-design, edit-prd, validate-prd,
check-implementation-readiness, create-architecture,
create-epics-and-stories, generate-project-context, correct-course,
create-story, qa-generate-e2e-tests, retrospective.

* fix(skills): address PR review findings on workflow customization

- bmad-create-story: drop stale {project_context} variable reference
  from step 2 note; content is already loaded via persistent_facts
- research skills (market/domain/technical): derive research_topic_slug
  before writing output filename to prevent path injection and invalid
  filesystem characters
- bmad-correct-course: reconcile step 1 verify list and HALT with the
  "Missing documents" rule — Architecture and UI/UX are optional, HALT
  only fires when PRD or Epics are missing
- fix grammar in Micro-file Design bullets across 5 migrated skills
  (self contained → self-contained, adhere too 1 file → adhere to one
  file at a time)
- docs/how-to/customize-bmad.md: document workflow activation order
  and frame the baseline fields as a stable initial pass with targeted
  per-workflow customization points coming later
2026-04-20 20:10:22 -05:00
Can Tecim f70a9e0569
Merge branch 'main' into patch-1 2026-04-19 19:54:13 +03:00
Can Tecim 9794507384
fix: update allowed modifications 2026-04-19 22:07:27 +07:00
Can Tecim 18c6fb6b7c
Merge branch 'main' into patch-1 2026-04-19 17:54:02 +03:00
Can Tecim 84bf1b7bba
refactor: allow 'Marking Review Follow-ups'
Updated instructions to include 'Marking Review Follow-ups' in the modification areas.
2026-04-19 21:53:23 +07:00
Can Tecim e67672ae38
refactor: update dev-story workflow instructions 2026-04-19 21:49:23 +07:00
Can Tecim 02a8c203c6
refactor: fix findings section in step-04-present.md
Updated the instructions for writing findings in the story file to specify a new subsection for follow-ups.
2026-04-19 21:48:20 +07:00
Can Tecim c90bdce306
refactor: allow dev-story skill to modify Review Findings todo items 2026-04-03 04:48:01 +07:00
101 changed files with 9594 additions and 3510 deletions

View File

@ -75,6 +75,8 @@ The customization model is what lets this scale beyond a single developer.
Every agent ships a `customize.toml` with sensible defaults. Teams commit overrides to `_bmad/custom/bmad-agent-{role}.toml`. Individuals can layer personal preferences in `.user.toml` (gitignored). The resolver merges all three at activation time with predictable structural rules.
Most users never hand-author these files. The `bmad-customize` skill walks through picking the target, choosing agent vs workflow scope, authoring the override, and verifying the merge — so the customization surface stays accessible to anyone who understands their intent, not just those fluent in TOML.
Concrete example: a team commits a single file telling Amelia to always use the Context7 MCP tool for library docs and to fall back to Linear when a story isn't in the local epics list. Every dev workflow Amelia dispatches (dev-story, quick-dev, create-story, code-review) inherits that behavior, with no source edits or per-workflow duplication required.
There's also a second customization surface for *cross-cutting* concerns: the central `_bmad/config.toml` and `_bmad/config.user.toml` (both installer-owned, rebuilt from each module's `module.yaml`) plus `_bmad/custom/config.toml` (team, committed) and `_bmad/custom/config.user.toml` (personal, gitignored) for overrides. This is where the **agent roster** lives — the lightweight descriptors that roster consumers like `bmad-party-mode`, `bmad-retrospective`, and `bmad-advanced-elicitation` read to know who's available and how to embody them. Rebrand an agent org-wide with a team override; add fictional voices (Kirk, Spock, a domain expert persona) as personal experiments via the `.user.toml` override — without touching any skill folder. The per-skill file shapes how Mary *behaves* when she activates; the central config shapes how other skills *see* her when they look at the field.
@ -83,6 +85,7 @@ For the full customization surface and worked examples, see:
- [How to Customize BMad](../how-to/customize-bmad.md) — the reference for what's customizable and how merge works
- [How to Expand BMad for Your Organization](../how-to/expand-bmad-for-your-org.md) — five worked recipes spanning agent-wide rules, workflow conventions, external publishing, template swaps, and agent roster customization
- `bmad-customize` skill — the guided authoring helper that turns intent into a correctly-placed, verified override file
## The Bigger Idea

View File

@ -7,6 +7,10 @@ sidebar:
Tailor agent personas, inject domain context, add capabilities, and configure workflow behavior -- all without modifying installed files. Your customizations survive every update.
:::tip[Don't want to hand-author TOML? Use `bmad-customize`]
The `bmad-customize` skill is a guided authoring helper for the **per-skill agent/workflow override surface** described in this doc. It scans what's customizable in your installation, helps you choose the right surface (agent vs workflow) for your intent, writes the override file for you, and verifies the merge landed. Central-config overrides (`_bmad/custom/config.toml`) are out of scope for v1 — hand-author those per the Central Configuration section below. Run the skill whenever you want to make a per-skill change; this doc is the reference for *what* each surface exposes and how merging works.
:::
## When to Use This
- You want to change an agent's personality or communication style
@ -258,6 +262,27 @@ on_complete = "Summarize the brief in three bullets and offer to email it via th
The same field conventions cross the agent/workflow boundary: `activation_steps_prepend`/`activation_steps_append`, `persistent_facts` (with `file:` refs), and menu-style `[[…]]` tables with `code`/`id` for keyed merge. The resolver applies the same four structural rules regardless of the top-level key. SKILL.md references follow the namespace: `{workflow.activation_steps_prepend}`, `{workflow.persistent_facts}`, `{workflow.on_complete}`. Any additional fields a workflow exposes (output paths, toggles, review settings, stage flags) follow the same shape-based merge rules. Read the workflow's `customize.toml` to see what's customizable.
### Activation Order
Customizable workflows run their activation in a fixed sequence so you know exactly when your hooks fire:
1. Resolve the `[workflow]` block (base → team → user merge)
2. Execute `activation_steps_prepend` in order
3. Load `persistent_facts` as foundational context for the run
4. Load config (`_bmad/bmm/config.yaml`) and resolve standard variables (project name, languages, paths, date)
5. Greet the user
6. Execute `activation_steps_append` in order
After step 6 the workflow body begins. Use `activation_steps_prepend` when you need context loaded before the greeting can be personalized; use `activation_steps_append` when the setup is heavy and you'd rather the user sees the greeting first.
### Scope of This Initial Pass
Customization is rolling out incrementally. The fields documented above — `activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete` — are the **baseline surface** that every customizable workflow exposes, and they will remain stable across versions. They give you broad-stroke control today: inject pre/post steps, pin foundational context, trigger follow-up actions.
Over time, individual workflows will expose **more targeted customization points** tailored to what that workflow actually does — things like step-specific toggles, stage flags, output template paths, or review gates. When those arrive, they stack on top of the baseline fields rather than replacing them, so customizations you author today keep working.
If you need a fine-grained knob that isn't exposed yet, either use `activation_steps_*` and `persistent_facts` to steer behavior, or open an issue describing the specific customization point you want — those requests are what drive which targeted fields get added next.
## Central Configuration
Per-skill `customize.toml` covers **deep behavior** (hooks, menus, persistent_facts, persona overrides for a single agent or workflow). A separate surface covers **cross-cutting state** — install answers and the agent roster that external skills like `bmad-party-mode`, `bmad-retrospective`, and `bmad-advanced-elicitation` consume. That surface lives in four TOML files at project root:
@ -362,7 +387,8 @@ For enterprise-oriented recipes (shaping an agent across every workflow it dispa
**Need to see what's customizable?**
- Read the skill's `customize.toml` -- every field there is customizable (except `name` and `title`)
- Run the `bmad-customize` skill — it enumerates every customizable skill installed in your project, shows which ones already have overrides, and walks you through adding or updating one
- Or read the skill's `customize.toml` directly — every field there is customizable (except `name` and `title`)
**Need to reset?**

View File

@ -14,6 +14,10 @@ BMad's customization surface lets an organization reshape behavior without editi
- Python 3.11+ on PATH (for the resolver — stdlib only, no `pip install`)
:::
:::tip[Applying these recipes]
The **per-skill recipes** below (Recipes 14) can be applied by running the `bmad-customize` skill and describing the intent — it will pick the right surface, author the override file, and verify the merge. Recipe 5 (central-config overrides to the agent roster) is out of scope for v1 of the skill and remains hand-authored. The recipes here are the source of truth for *what* to override; `bmad-customize` handles the *how* for the agent/workflow surface.
:::
## The Three-Layer Mental Model
Before picking a recipe, know where your override lands:

View File

@ -1,122 +1,226 @@
---
title: 'How to Install BMad'
description: Step-by-step guide to installing BMad in your project
description: Install, update, and pin BMad for local development, teams, and CI
sidebar:
order: 1
---
Use the `npx bmad-method install` command to set up BMad in your project with your choice of modules and AI tools.
If you want to use a non interactive installer and provide all install options on the command line, see [this guide](./non-interactive-installation.md).
Use `npx bmad-method install` to set up BMad in your project. One command handles first installs, upgrades, channel switching, and scripted CI runs. This page covers all of it.
## When to Use This
- Starting a new project with BMad
- Adding BMad to an existing codebase
- Update the existing BMad Installation
- Adding or removing modules on an existing install
- Switching a module to main-HEAD or pinning to a specific release
- Scripting installs for CI pipelines, Dockerfiles, or enterprise rollouts
:::note[Prerequisites]
- **Node.js** 20+ (required for the installer)
- **Git** (recommended)
- **AI tool** (Claude Code, Cursor, or similar)
:::
- **Node.js** 20+ (the installer requires it)
- **Git** (for cloning external modules)
- **An AI tool** such as Claude Code or Cursor — or install without one using `--tools none`
## Steps
:::
### 1. Run the Installer
## First-time install (the fast path)
```bash
npx bmad-method install
```
:::tip[Want the newest prerelease build?]
Use the `next` dist-tag:
The interactive flow asks you five things:
1. Installation directory (defaults to the current working directory)
2. Which modules to install (checkboxes for core, bmm, bmb, cis, gds, tea)
3. **"Ready to install (all stable)?"** — Yes accepts the latest released tag for every external module
4. Which AI tools/IDEs to integrate with (claude-code, cursor, and others)
5. Per-module config (name, language, output folder)
Accept the defaults and you land on the latest stable release of every module, configured for your chosen tool.
:::tip[Just want the newest prerelease?]
```bash
npx bmad-method@next install
```
This gets you newer changes earlier, with a higher chance of churn than the default install.
Runs the prerelease installer, which ships a newer snapshot of core and bmm. More churn, fewer delays between development and release.
:::
:::tip[Bleeding edge]
To install the latest from the main branch (may be unstable):
## Picking a specific version
Two independent axes control what ends up on disk.
### Axis 1: external module channels
Every external module — bmb, cis, gds, tea, and any community module — installs on one of three channels:
| Channel | What gets installed | Who picks this |
| ------------------ | ---------------------------------------------------------------------------- | --------------------------------------- |
| `stable` (default) | Highest released semver tag. Prereleases like `v2.0.0-alpha.1` are excluded. | Most users |
| `next` | Main branch HEAD at install time | Contributors, early adopters |
| `pinned` | A specific tag you name | Enterprise installs, CI reproducibility |
Channels are per-module. You can run bmb on `next` while leaving cis on `stable` — the flags below let you mix freely.
### Axis 2: installer binary version
The `bmad-method` npm package itself has two dist-tags:
| Command | What you get |
| ------------------------------------- | ----------------------------------------------------------------- |
| `npx bmad-method install` (`@latest`) | Latest stable installer release |
| `npx bmad-method@next install` | Latest prerelease installer, auto-published on every push to main |
**The installer binary determines your core and bmm versions.** Those two modules ship bundled inside the installer package rather than being cloned from separate repos.
### Why core and bmm don't have their own channel
They're stapled to the installer binary you ran:
- `npx bmad-method install` → latest stable core and bmm
- `npx bmad-method@next install` → prerelease core and bmm
- `node /path/to/local-checkout/tools/installer/bmad-cli.js install` → whatever your local checkout has
`--pin bmm=v6.3.0` and `--next=bmm` are silently ineffective against bundled modules, and the installer warns you when you try. A future release extracts bmm from the installer package; once that ships, bmm gets a proper channel selector like bmb has today.
## Updating an existing install
Running `npx bmad-method install` in a directory that already contains `_bmad/` gives you a menu:
| Choice | What it does |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Quick Update** | Re-runs the install with your existing settings. Refreshes files, applies patches and minor stable upgrades, refuses major upgrades. Fast, non-interactive. |
| **Modify Install** | Full interactive flow. Add or remove modules, reconfigure settings, optionally review and switch channels for existing modules. |
### Upgrade prompts
When Modify detects a newer stable tag for a module you've installed on `stable`, it classifies the diff and prompts accordingly:
| Upgrade type | Example | Default |
| ------------ | --------------- | ------- |
| Patch | v1.7.0 → v1.7.1 | Y |
| Minor | v1.7.0 → v1.8.0 | Y |
| Major | v1.7.0 → v2.0.0 | **N** |
Major defaults to N because breaking changes frequently surface as "instability" when they weren't expected. The prompt includes a GitHub release-notes URL so you can read what changed before accepting.
Under `--yes`, patch and minor upgrades apply automatically. Majors stay frozen — pass `--pin <code>=<new-tag>` to accept non-interactively.
### Switching a module's channel
**Interactively:** choose Modify → answer **Yes** to "Review channel assignments?" → each external module offers Keep, Switch to stable, Switch to next, or Pin to a tag.
**Via flags:** the recipes in the next section cover the common cases.
## Headless CI installs
### Flag reference
| Flag | Purpose |
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| `--yes`, `-y` | Skip all prompts; accept flag values + defaults |
| `--directory <path>` | Install into this directory (default: current working dir) |
| `--modules <a,b,c>` | Exact module set. Core is auto-added. Not a delta — list everything you want kept. |
| `--tools <a,b>` or `--tools none` | IDE/tool selection. `none` skips tool config entirely. |
| `--action <type>` | `install`, `update`, or `quick-update`. Defaults based on existing install state. |
| `--custom-source <urls>` | Install custom modules from Git URLs or local paths |
| `--channel <stable\|next>` | Apply to all externals (aliased as `--all-stable` / `--all-next`) |
| `--all-stable` | Alias for `--channel=stable` |
| `--all-next` | Alias for `--channel=next` |
| `--next=<code>` | Put one module on next. Repeatable. |
| `--pin <code>=<tag>` | Pin one module to a specific tag. Repeatable. |
| `--user-name`, `--communication-language`, `--document-output-language`, `--output-folder` | Override per-user config defaults |
Precedence when flags overlap: `--pin` beats `--next=` beats `--channel` / `--all-*` beats the registry default (`stable`).
:::note[Example resolution]
`--all-next --pin cis=v0.2.0` puts bmb, gds, and tea on next while pinning cis to v0.2.0.
:::
### Recipes
**Default install — latest stable for everything:**
```bash
npx github:bmad-code-org/BMAD-METHOD install
npx bmad-method install --yes --modules bmm,bmb,cis --tools claude-code
```
**Enterprise pin — reproducible byte-for-byte:**
```bash
npx bmad-method install --yes \
--modules bmm,bmb,cis \
--pin bmb=v1.7.0 --pin cis=v0.2.0 \
--tools claude-code
```
**Bleeding edge — externals on main HEAD:**
```bash
npx bmad-method install --yes --modules bmm,bmb --all-next --tools claude-code
```
**Add a module to an existing install** (keep everything else):
```bash
npx bmad-method install --yes --action update \
--modules bmm,bmb,gds \
--tools none
```
**Mix channels — bmb on next, gds on stable:**
```bash
npx bmad-method install --yes --action update \
--modules bmm,bmb,cis,gds \
--next=bmb \
--tools none
```
:::caution[Rate limit on shared IPs]
Anonymous GitHub API calls are capped at 60/hour per IP. A single install hits the API once per external module to resolve the stable tag. Offices behind NAT, CI runner pools, and VPNs can collectively exhaust this.
Set `GITHUB_TOKEN=<personal access token>` in the environment to raise the limit to 5000/hour per account. Any public-repo-read PAT works; no scopes are required.
:::
### 2. Choose Installation Location
## What got installed
The installer will ask where to install BMad files:
After any install, `_bmad/_config/manifest.yaml` records exactly what's on disk:
- Current directory (recommended for new projects if you created the directory yourself and ran from within the directory)
- Custom path
### 3. Select Your AI Tools
Pick which AI tools you use:
- Claude Code
- Cursor
- Others
Each tool has its own way of integrating skills. The installer creates tiny prompt files to activate workflows and agents — it just puts them where your tool expects to find them.
:::note[Enabling Skills]
Some platforms require skills to be explicitly enabled in settings before they appear. If you install BMad and don't see the skills, check your platform's settings or ask your AI assistant how to enable skills.
:::
### 4. Choose Modules
The installer shows available modules. Select whichever ones you need — most users just want **BMad Method** (the software development module).
### 5. Follow the Prompts
The installer guides you through the rest — settings, tool integrations, etc.
## What You Get
```text
your-project/
├── _bmad/
│ ├── bmm/ # Your selected modules
│ │ └── config.yaml # Module settings (if you ever need to change them)
│ ├── core/ # Required core module
│ └── ...
├── _bmad-output/ # Generated artifacts
├── .claude/ # Claude Code skills (if using Claude Code)
│ └── skills/
│ ├── bmad-help/
│ ├── bmad-persona/
│ └── ...
└── .cursor/ # Cursor skills (if using Cursor)
└── skills/
└── ...
```yaml
modules:
- name: bmb
version: v1.7.0 # the tag, or "main" for next
channel: stable # stable | next | pinned
sha: 86033fc9aeae2ca6d52c7cdb675c1f4bf17fc1c1
source: external
repoUrl: https://github.com/bmad-code-org/bmad-builder
```
## Verify Installation
The `sha` field is written for git-backed modules (external, community, and URL-based custom). Bundled modules (core, bmm) and local-path custom modules don't have one — their code travels with the installer binary or your filesystem, not a cloneable ref.
Run `bmad-help` to verify everything works and see what to do next.
For cross-machine reproducibility, don't rely on rerunning the same `--modules` command. Stable-channel installs resolve to the highest released tag **at install time**, so a later rerun lands on whatever has been released since. Convert the recorded tags from `manifest.yaml` into explicit `--pin` flags on the target machine, e.g.:
**BMad-Help is your intelligent guide** that will:
- Confirm your installation is working
- Show what's available based on your installed modules
- Recommend your first step
You can also ask it questions:
```
bmad-help I just installed, what should I do first?
bmad-help What are my options for a SaaS project?
```bash
npx bmad-method install --yes --modules bmb,cis \
--pin bmb=v1.7.0 --pin cis=v0.4.2 --tools none
```
## Troubleshooting
**Installer throws an error** — Copy-paste the output into your AI assistant and let it figure it out.
### "Could not resolve stable tag" or "API rate limit exceeded"
**Installer worked but something doesn't work later** — Your AI needs BMad context to help. See [How to Get Answers About BMad](./get-answers-about-bmad.md) for how to point your AI at the right sources.
You've hit GitHub's 60/hr anonymous limit. Set `GITHUB_TOKEN` and retry. If you already have a token set, it may be expired or rate-limited on its own budget — try a different token or wait for the hourly reset.
### "Tag 'vX.Y.Z' not found"
The tag you passed to `--pin` doesn't exist in the module's repo. Check the repo's releases page on GitHub for valid tags.
### A pinned install keeps upgrading
Pinned installs don't upgrade. Quick-update applies patches and minors on stable channel only; it won't touch `pinned` or `next`. If a pinned install changed, open `_bmad/_config/manifest.yaml``channel: pinned` plus a fixed `version` and `sha` should hold across runs unless you explicitly override via flags.
### `--pin bmm=X` didn't do anything
bmm is a bundled module — `--pin` and `--next=` don't apply. Use `npx bmad-method@next install` for a prerelease core/bmm, or check out the bmad-bmm repo and run the installer locally to get unreleased changes.

View File

@ -1,196 +1,10 @@
---
title: Non-Interactive Installation
description: Install BMad using command-line flags for CI/CD pipelines and automated deployments
description: Headless / CI install docs have moved
sidebar:
order: 2
---
Use command-line flags to install BMad non-interactively. This is useful for:
## When to Use This
- Automated deployments and CI/CD pipelines
- Scripted installations
- Batch installations across multiple projects
- Quick installations with known configurations
:::note[Prerequisites]
Requires [Node.js](https://nodejs.org) v20+ and `npx` (included with npm).
:::
## Available Flags
### Installation Options
| Flag | Description | Example |
| --------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------- |
| `--directory <path>` | Installation directory | `--directory ~/projects/myapp` |
| `--modules <modules>` | Comma-separated module IDs | `--modules bmm,bmb` |
| `--tools <tools>` | Comma-separated tool/IDE IDs (use `none` to skip) | `--tools claude-code,cursor` or `--tools none` |
| `--action <type>` | Action for existing installations: `install` (default), `update`, or `quick-update` | `--action quick-update` |
| `--custom-source <sources>` | Comma-separated Git URLs or local paths for custom modules | `--custom-source /path/to/module` |
### Core Configuration
| Flag | Description | Default |
| ----------------------------------- | ----------------------------------------------- | --------------- |
| `--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 (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
| Flag | Description |
| ------------- | ------------------------------------------- |
| `-y, --yes` | Accept all defaults and skip prompts |
| `-d, --debug` | Enable debug output for manifest generation |
## Module IDs
Available module IDs for the `--modules` flag:
- `bmm` — BMad Method Master
- `bmb` — BMad Builder
Check the [BMad registry](https://github.com/bmad-code-org) for available external modules.
## Tool/IDE IDs
Available tool IDs for the `--tools` flag:
**Preferred:** `claude-code`, `cursor`
Run `npx bmad-method install` interactively once to see the full current list of supported tools, or check the [platform codes configuration](https://github.com/bmad-code-org/BMAD-METHOD/blob/main/tools/installer/ide/platform-codes.yaml).
## Installation Modes
| Mode | Description | Example |
| --------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Fully non-interactive | Provide all flags to skip all prompts | `npx bmad-method install --directory . --modules bmm --tools claude-code --yes` |
| Semi-interactive | Provide some flags; BMad prompts for the rest | `npx bmad-method install --directory . --modules bmm` |
| Defaults only | Accept all defaults with `-y` | `npx bmad-method install --yes` |
| Custom source only | Install core + custom module(s) | `npx bmad-method install --directory . --custom-source /path/to/module --tools claude-code --yes` |
| Without tools | Skip tool/IDE configuration | `npx bmad-method install --modules bmm --tools none` |
## Examples
### CI/CD Pipeline Installation
```bash
#!/bin/bash
# install-bmad.sh
npx bmad-method install \
--directory "${GITHUB_WORKSPACE}" \
--modules bmm \
--tools claude-code \
--user-name "CI Bot" \
--communication-language English \
--document-output-language English \
--output-folder _bmad-output \
--yes
```
### Update Existing Installation
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--action update \
--modules bmm,bmb,custom-module
```
### Quick Update (Preserve Settings)
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--action quick-update
```
### Install from Custom Source
Install a module from a local path or any Git host:
```bash
npx bmad-method install \
--directory . \
--custom-source /path/to/my-module \
--tools claude-code \
--yes
```
Combine with official modules:
```bash
npx bmad-method install \
--directory . \
--modules bmm \
--custom-source https://gitlab.com/myorg/my-module \
--tools claude-code \
--yes
```
:::note[Custom source behavior]
When `--custom-source` is used without `--modules`, only core and the custom modules are installed. Add `--modules` to include official modules as well. See [Install Custom and Community Modules](./install-custom-modules.md) for details.
:::
## What You Get
- A fully configured `_bmad/` directory in your project
- Agents and workflows configured for your selected modules and tools
- A `_bmad-output/` folder for generated artifacts
## Validation and Error Handling
BMad validates all provided flags:
- **Directory** — Must be a valid path with write permissions
- **Modules** — Warns about invalid module IDs (but won't fail)
- **Tools** — Warns about invalid tool IDs (but won't fail)
- **Action** — Must be one of: `install`, `update`, `quick-update`
Invalid values will either:
1. Show an error and exit (for critical options like directory)
2. Show a warning and skip (for optional items)
3. Fall back to interactive prompts (for missing required values)
:::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
:::
## Troubleshooting
### Installation fails with "Invalid directory"
- The directory path must exist (or its parent must exist)
- You need write permissions
- The path must be absolute or correctly relative to the current directory
### Module not found
- Verify the module ID is correct
- External modules must be available in the registry
:::note[Still stuck?]
Run with `--debug` for detailed output, try interactive mode to isolate the issue, or report at <https://github.com/bmad-code-org/BMAD-METHOD/issues>.
:::note[This page has moved]
Headless and CI install flags, channel selection, and pinning now live in the unified [How to Install BMad](./install-bmad.md) guide. Jump to the [Headless / CI installs](./install-bmad.md#headless-ci-installs) section for the flag reference and copy-paste recipes.
:::

View File

@ -0,0 +1,826 @@
---
title: Hướng dẫn BMAD cho Developer
description: Tài liệu tổng quan bằng tiếng Việt dành cho developer muốn áp dụng BMAD Method từ ý tưởng đến triển khai
---
# BMAD Method — Hướng dẫn toàn diện cho Developer
> **BMAD** (Build More Architect Dreams) là framework phát triển phần mềm hỗ trợ bởi AI, giúp team đi từ ý tưởng đến sản phẩm một cách có cấu trúc, nhất quán và hiệu quả.
---
## Mục lục
1. [BMAD là gì?](#1-bmad-là-gì)
2. [Nguyên lý cốt lõi](#2-nguyên-lý-cốt-lõi)
3. [Kiến trúc hệ thống — Các Agent](#3-kiến-trúc-hệ-thống--các-agent)
4. [Quy trình làm việc — 4 Giai đoạn](#4-quy-trình-làm-việc--4-giai-đoạn)
5. [Chọn nhánh phù hợp](#5-chọn-nhánh-phù-hợp)
6. [Hướng dẫn từng bước áp dụng BMAD](#6-hướng-dẫn-từng-bước-áp-dụng-bmad)
7. [Kiểm thử với BMAD — Hướng dẫn cho QC](#7-kiểm-thử-với-bmad--hướng-dẫn-cho-qc)
8. [Các công cụ hỗ trợ](#8-các-công-cụ-hỗ-trợ)
9. [Cấu trúc thư mục dự án](#9-cấu-trúc-thư-mục-dự-án)
10. [Mẹo và Best Practices](#10-mẹo-và-best-practices)
---
## 1. BMAD là gì?
**BMAD Method** là một hệ thống phối hợp nhiều AI agent chuyên biệt để hỗ trợ toàn bộ vòng đời phát triển phần mềm — từ phân tích ý tưởng, lập kế hoạch, thiết kế kiến trúc, đến triển khai code và kiểm thử.
### Điểm khác biệt so với cách dùng AI thông thường
| Cách thông thường | BMAD Method |
|---|---|
| Hỏi AI từng câu rời rạc | Workflow có cấu trúc, mỗi bước tạo đầu ra cho bước kế tiếp |
| Một AI làm tất cả | Nhiều agent chuyên biệt, mỗi agent hiểu sâu vai trò của mình |
| Không có tài liệu hóa | Mỗi giai đoạn sinh ra tài liệu chuẩn (PRD, Architecture, Stories) |
| Developer phải giám sát liên tục | Agent tự chủ dài hơn, chỉ cần con người tại các điểm kiểm tra quan trọng |
### BMAD phù hợp với ai?
- **Developer** cần xây dựng tính năng nhanh, chất lượng cao
- **Tech Lead / Architect** cần thiết kế hệ thống và phân rã công việc
- **Product Manager** cần định nghĩa yêu cầu rõ ràng
- **QC/Tester** cần sinh test case có truy vết yêu cầu
- **Team nhỏ** muốn áp dụng quy trình chuẩn không cần nhiều overhead
---
## 2. Nguyên lý cốt lõi
### 2.1. Tài liệu là "ngôn ngữ chung" giữa con người và AI
Mỗi giai đoạn trong BMAD sinh ra một tài liệu chuẩn. Tài liệu đó trở thành **đầu vào** cho giai đoạn kế tiếp. Agent AI đọc tài liệu để hiểu context, thay vì phụ thuộc vào lịch sử hội thoại có thể bị mất.
```
Ý tưởng → [Brief/PRFAQ] → PRD → Architecture → Epics/Stories → Code → Tests
```
### 2.2. Phân tách "XÂY GÌ" và "XÂY NHƯ THẾ NÀO"
BMAD tách bạch rõ ràng hai câu hỏi quan trọng nhất:
- **Planning (Giai đoạn 2)**: Trả lời **"XÂY GÌ và vì sao?"** → Đầu ra: PRD
- **Solutioning (Giai đoạn 3)**: Trả lời **"XÂY NHƯ THẾ NÀO?"** → Đầu ra: Architecture + Epics/Stories
> Đây là nguyên lý quan trọng nhất. Nhiều dự án thất bại vì triển khai khi chưa thống nhất được "XÂY GÌ", hoặc bắt đầu code mà chưa quyết định "XÂY NHƯ THẾ NÀO".
### 2.3. Agent chuyên biệt — mỗi vai trò một chuyên gia
BMAD không dùng một AI đa năng mà dùng các agent được cấu hình để đóng vai chuyên gia cụ thể: PM, Architect, Developer, UX Designer, Technical Writer. Mỗi agent có phong cách tư duy, ưu tiên, và workflow riêng.
### 2.4. Con người chỉ tham gia tại các điểm kiểm tra quan trọng
BMAD được thiết kế để AI tự chủ trong phạm vi đã định nghĩa, chỉ đưa con người vào:
- Phê duyệt chuyển giai đoạn (PRD xong → Architect làm việc)
- Review kết quả tổng thể (sau Dev Story, sau epic)
- Quyết định thay đổi hướng (Correct Course)
### 2.5. Có thể mở rộng theo nhu cầu
Ba nhánh lập kế hoạch với độ phức tạp tăng dần:
| Nhánh | Phù hợp với | Story ước tính |
|---|---|---|
| **Quick Flow** | Bug fix, tính năng nhỏ, phạm vi rõ | 115 stories |
| **BMad Method** | Sản phẩm, nền tảng, tính năng phức tạp | 1050+ stories |
| **Enterprise** | Hệ thống tuân thủ, đa tenant, đa team | 30+ stories |
---
## 3. Kiến trúc hệ thống — Các Agent
### 3.1. Các Agent chính
| Agent | Tên nhân vật | Skill ID | Vai trò |
|---|---|---|---|
| **Analyst** | Mary | `bmad-analyst` | Brainstorm, nghiên cứu thị trường/kỹ thuật, tạo Product Brief và PRFAQ |
| **Product Manager** | John | `bmad-pm` | Tạo và quản lý PRD, Epics, Stories, kiểm tra Implementation Readiness |
| **Architect** | Winston | `bmad-architect` | Thiết kế Architecture, ADR, kiểm tra Implementation Readiness |
| **Developer** | Amelia | `bmad-agent-dev` | Triển khai story, tạo test, code review, sprint planning |
| **UX Designer** | Sally | `bmad-ux-designer` | Thiết kế UX specification |
| **Technical Writer** | Paige | `bmad-tech-writer` | Viết tài liệu, cập nhật standards, giải thích khái niệm |
### 3.2. Cách gọi Agent
**Qua Skill** (Claude Code / Cursor):
```
bmad-analyst
bmad-pm
bmad-architect
bmad-agent-dev
```
**Qua Trigger** (sau khi đã nạp agent, gõ mã ngắn trong hội thoại):
| Trigger | Agent | Workflow |
|---|---|---|
| `BP` | Analyst | Brainstorm |
| `CB` | Analyst | Create Brief |
| `CP` | PM | Create PRD |
| `VP` | PM | Validate PRD |
| `EP` | PM | Create Epics & Stories |
| `CA` | Architect | Create Architecture |
| `IR` | PM / Architect | Implementation Readiness |
| `SP` | Developer | Sprint Planning |
| `DS` | Developer | Dev Story |
| `QA` | Developer | QA Test Generation |
| `CR` | Developer | Code Review |
---
## 4. Quy trình làm việc — 4 Giai đoạn
```
┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Giai đoạn 1 │ │ Giai đoạn 2 │ │ Giai đoạn 3 │ │ Giai đoạn 4 │
│ PHÂN TÍCH │───▶│ LẬP KẾ HOẠCH │───▶│ ĐỊNH HÌNH GIẢI │───▶│ TRIỂN KHAI │
│ (Tùy chọn) │ │ (Bắt buộc) │ │ PHÁP (BMad/Ent) │ │ (Bắt buộc) │
│ │ │ │ │ │ │ │
│ Brief, PRFAQ │ │ PRD, UX Spec │ │ Architecture, │ │ Sprint, Stories, │
│ Research │ │ │ │ Epics, Stories │ │ Code, Test, QA │
└─────────────────┘ └─────────────────┘ └──────────────────┘ └─────────────────┘
```
### Giai đoạn 1: Phân tích (Tùy chọn)
Giai đoạn này giúp khám phá và xác nhận ý tưởng **trước khi** cam kết lập kế hoạch chi tiết. Bỏ qua nếu yêu cầu đã rõ.
**Các công cụ:**
**Brainstorming** — Khi cần khai phá ý tưởng
```
Trigger: BP (trong agent Analyst)
Đầu ra: brainstorming-report.md
```
Sử dụng 60+ kỹ thuật brainstorming, tạo 100+ ý tưởng đa dạng, sau đó phân tích, lọc và đề xuất hướng tiếp cận.
**Product Brief** — Khi concept đã tương đối rõ
```
Trigger: CB (trong agent Analyst)
Đầu ra: product-brief.md
```
Tóm tắt điều hành 12 trang: vấn đề, giải pháp, đối tượng, lợi thế cạnh tranh, rủi ro.
**PRFAQ** — Khi cần stress-test concept
```
Trigger: (hỏi Analyst về PRFAQ)
Đầu ra: prfaq.md
```
Phương pháp "Working Backwards" của Amazon: viết thông cáo báo chí như thể sản phẩm đã tồn tại, sau đó trả lời các câu hỏi khó nhất từ khách hàng. Buộc phải rõ ràng theo hướng lấy khách hàng làm trung tâm.
**Nghiên cứu** — Xác thực giả định
```
Trigger: MR (Market Research), DR (Domain Research), TR (Technical Research)
```
---
### Giai đoạn 2: Lập kế hoạch (Bắt buộc)
Xác định rõ **cần xây gì****cho ai**.
**Tạo PRD** — PM Agent
```
Trigger: CP
Đầu ra: PRD.md
```
PRD bao gồm: mục tiêu sản phẩm, functional requirements (FR), non-functional requirements (NFR), user stories cấp cao, acceptance criteria.
**Thiết kế UX** — UX Designer Agent (Tùy chọn)
```
Trigger: CU
Đầu ra: ux-spec.md
```
Dùng khi UX/UI là yếu tố quan trọng. Bao gồm user flows, component specs, interaction patterns.
**Validate PRD** — PM Agent
```
Trigger: VP
```
Kiểm tra tính đầy đủ, nhất quán, và khả năng triển khai của PRD trước khi chuyển sang giai đoạn 3.
---
### Giai đoạn 3: Định hình giải pháp (Bắt buộc với BMad Method / Enterprise)
Quyết định **xây như thế nào** và phân rã công việc.
**Tạo Architecture** — Architect Agent
```
Trigger: CA
Đầu ra: architecture.md + ADR (Architecture Decision Records)
```
Bao gồm: tech stack, component design, data models, API contracts, deployment strategy, ADR cho các quyết định quan trọng.
**Tạo Epics & Stories** — PM Agent
```
Trigger: EP
Đầu ra: epics/ thư mục với các file story
```
Phân rã PRD và Architecture thành Epics (nhóm tính năng) và Stories (đơn vị công việc cụ thể). Mỗi story có: mô tả, acceptance criteria, technical notes.
**Implementation Readiness Check** — Architect Agent
```
Trigger: IR
Kết quả: PASS / CONCERNS / FAIL
```
Cổng kiểm tra trước khi bắt đầu triển khai. Đảm bảo mọi thứ đã đủ rõ ràng để developer có thể làm việc độc lập.
---
### Giai đoạn 4: Triển khai (Bắt buộc)
Xây dựng từng story một theo thứ tự ưu tiên.
**Sprint Planning** — Developer Agent
```
Trigger: SP
Đầu ra: sprint-status.yaml
```
Xác định stories sẽ làm trong sprint, thứ tự ưu tiên và tracking.
**Dev Story** — Developer Agent
```
Trigger: DS
Đầu ra: Code chạy được + unit/integration tests
```
Agent tự chủ triển khai story theo acceptance criteria. Đọc architecture và project-context để đảm bảo nhất quán.
**Code Review** — Developer Agent
```
Trigger: CR
Kết quả: Approved / Changes Requested
```
Review tự động: correctness, style, security, performance, test coverage.
**QA Test Generation** — Developer Agent
```
Trigger: QA
Đầu ra: API tests + E2E tests
```
Sinh test case cho API và E2E sau khi epic hoàn tất. Chi tiết ở [Mục 7](#7-kiểm-thử-với-bmad--hướng-dẫn-cho-qc).
**Correct Course** — PM Agent
```
Trigger: CC
```
Xử lý thay đổi yêu cầu lớn giữa sprint mà không phá vỡ quy trình.
**Retrospective** — Developer Agent
```
Trigger: ER (Epic Retrospective)
```
Review sau khi hoàn tất một epic. Ghi lại bài học, pattern tốt, vấn đề gặp phải.
---
## 5. Chọn nhánh phù hợp
### Quick Flow — Nhánh nhanh
**Khi nào dùng:**
- Bug fix
- Tính năng nhỏ, phạm vi rõ ràng
- Cập nhật đơn lẻ (115 stories)
- Bạn đã hiểu đầy đủ yêu cầu
**Bỏ qua:** Giai đoạn 1, 2, 3 hoàn toàn
**Dùng:** Quick Dev (`bmad-quick-dev`)
```
Mô tả yêu cầu → Làm rõ ý định → Sinh spec → Triển khai → Review → Done
```
Quick Dev gộp tất cả vào một workflow: làm rõ yêu cầu, lập kế hoạch mini, triển khai, code review, và trình bày kết quả.
---
### BMad Method — Nhánh đầy đủ
**Khi nào dùng:**
- Sản phẩm mới hoặc nền tảng
- Tính năng phức tạp với nhiều dependencies
- 1050+ stories cần phối hợp nhiều developer
**Đi qua:** Giai đoạn 1 (tùy chọn) → 2 → 3 → 4
---
### Enterprise — Nhánh mở rộng
**Khi nào dùng:**
- Hệ thống đa tenant
- Yêu cầu tuân thủ (compliance), security audit
- 30+ stories, nhiều team
- Cần truy vết yêu cầu đầy đủ
**Thêm vào:** Security review, DevOps pipeline, NFR assessment, Test Architect Module (TEA)
---
## 6. Hướng dẫn từng bước áp dụng BMAD
### 6.1. Dự án mới
#### Bước 1: Cài đặt BMAD
```bash
# Yêu cầu: Node.js 20+, Git
npx bmad-method install
```
Trình cài đặt sẽ hỏi:
- IDE đang dùng (Claude Code, Cursor, hoặc tương tự)
- Modules muốn cài (core bắt buộc, thêm TEA nếu cần test nâng cao)
- Nhánh lập kế hoạch (Quick Flow / BMad Method / Enterprise)
#### Bước 2: Khởi động với bmad-help
```
bmad-help
```
Đây là điểm bắt đầu thông minh. Agent sẽ hỏi về dự án của bạn và dẫn bạn đến đúng workflow.
```
bmad-help Tôi có ý tưởng về ứng dụng SaaS quản lý task, bắt đầu từ đâu?
bmad-help Tôi cần thêm tính năng export PDF, dùng quick flow hay đầy đủ?
```
#### Bước 3: Tạo Project Context (khuyến nghị mạnh)
```bash
# Tạo tự động sau khi có architecture
bmad-generate-project-context
# Hoặc tạo thủ công
touch _bmad-output/project-context.md
```
File `project-context.md` là "bản hiến pháp" kỹ thuật của dự án — được tất cả agent tự động nạp:
```markdown
# Project Context
## Technology Stack
- Node.js 20.x, TypeScript 5.3
- React 18.2, Zustand (không dùng Redux)
- PostgreSQL 15, Prisma ORM
- Testing: Vitest, Playwright, MSW
## Critical Implementation Rules
- Bật strict mode — không dùng `any`
- Dùng `interface` cho public API, `type` cho union/intersection
- API calls phải qua `apiClient` singleton
- Components đặt trong `/src/components/` với co-located tests
```
#### Bước 4: Chạy Analysis (nếu cần)
```bash
# Mở agent Analyst
bmad-analyst
# Trong hội thoại, gõ trigger:
BP # Brainstorm ý tưởng
CB # Tạo Product Brief
MR # Research thị trường
```
#### Bước 5: Tạo PRD
```bash
# Mở agent PM
bmad-pm
# Trigger tạo PRD
CP # Create PRD (có hướng dẫn từng bước)
VP # Validate PRD sau khi hoàn thiện
```
#### Bước 6: Tạo Architecture (BMad Method / Enterprise)
```bash
# Mở agent Architect
bmad-architect
# Trigger
CA # Create Architecture
IR # Implementation Readiness Check
```
#### Bước 7: Tạo Epics & Stories
```bash
# Mở agent PM
bmad-pm
# Trigger
EP # Create Epics and Stories
```
#### Bước 8: Triển khai theo Stories
```bash
# Mở agent Developer
bmad-agent-dev
# Mỗi sprint
SP # Sprint Planning
DS # Dev Story (làm từng story)
CR # Code Review
QA # Tạo tests (sau khi epic hoàn tất)
ER # Epic Retrospective
```
---
### 6.2. Dự án đã tồn tại
#### Bước 1: Tạo Project Context từ codebase hiện tại
```bash
# Chạy trong agent Developer hoặc Architect
bmad-generate-project-context
```
Agent sẽ khám phá codebase và tạo `project-context.md` từ:
- `package.json`, `pyproject.toml`, hoặc build files
- Cấu trúc thư mục
- Conventions hiện có trong code
#### Bước 2: Tạo tài liệu index
Tạo hoặc cập nhật `docs/index.md` với:
- Mục tiêu kinh doanh của dự án
- Architecture overview
- Các quy tắc quan trọng cần giữ
#### Bước 3: Chọn cách tiếp cận phù hợp
- **Thay đổi nhỏ** (bug fix, tính năng nhỏ): Dùng `bmad-quick-dev` trực tiếp
- **Thay đổi lớn** (module mới, refactor lớn): Dùng BMad Method đầy đủ từ Giai đoạn 2
#### Bước 4: Quick Dev cho việc nhỏ
```bash
# Mở skill Quick Dev
bmad-quick-dev
# Mô tả yêu cầu, agent sẽ:
# 1. Làm rõ ý định (có người trong vòng lặp)
# 2. Tạo mini-spec nếu cần
# 3. Triển khai tự động
# 4. Code review
# 5. Trình bày kết quả để bạn approve
```
---
### 6.3. Luồng làm việc mẫu — Tính năng mới (BMad Method)
```
Ngày 1-2: Analysis
├── bmad-analyst → CB → product-brief.md
└── (tùy chọn) bmad-analyst → MR → market-research.md
Ngày 2-3: Planning
├── bmad-pm → CP → PRD.md
├── bmad-pm → VP (validate)
└── (nếu có UI) bmad-ux-designer → CU → ux-spec.md
Ngày 3-4: Solutioning
├── bmad-architect → CA → architecture.md
├── bmad-pm → EP → epics/ (stories)
└── bmad-architect → IR → PASS ✓
Ngày 5+: Implementation (lặp lại cho mỗi story)
├── bmad-agent-dev → SP → sprint-status.yaml
├── bmad-agent-dev → DS → code + tests
├── bmad-agent-dev → CR → approved
└── (sau epic) bmad-agent-dev → QA → e2e tests
```
---
## 7. Kiểm thử với BMAD — Hướng dẫn cho QC
BMAD cung cấp hai hướng tiếp cận kiểm thử:
### 7.1. QA tích hợp sẵn — Nhẹ nhàng (Developer Agent)
**Phù hợp với:** Dự án nhỏtrung bình, cần bao phủ test nhanh
**Kích hoạt:**
```bash
# Trong agent Developer
bmad-agent-dev
# Sau khi hoàn tất một epic (tất cả stories đã dev + review xong)
QA # QA Test Generation
```
**5 bước workflow QA:**
1. **Phát hiện framework**: Agent tự nhận diện Jest, Vitest, Playwright, Cypress từ codebase
2. **Xác định tính năng cần test**: Dựa vào stories và acceptance criteria của epic vừa hoàn tất
3. **Tạo API tests**: Status codes, cấu trúc response, happy path, edge cases
4. **Tạo E2E tests**: User workflows, semantic locators (role/label/text — không dùng CSS selector)
5. **Chạy và xác minh**: Tự chạy tests, phát hiện và sửa lỗi ngay
**Các nguyên tắc khi sinh test:**
```typescript
// ✅ Dùng semantic locator
await page.getByRole('button', { name: 'Đăng nhập' }).click()
await page.getByLabel('Email').fill('user@example.com')
// ❌ Không dùng CSS selector cứng
await page.locator('.btn-primary#login').click()
// ✅ Test độc lập, không phụ thuộc thứ tự
test('create task', async () => {
// setup riêng cho test này
})
// ❌ Không hardcode wait/sleep
await page.waitForTimeout(3000) // Không làm thế này
```
**Khi nào dùng:**
- Cần bao phủ test nhanh cho tính năng mới
- Dự án nhỏtrung bình không cần chiến lược kiểm thử nâng cao
- Muốn tự động hóa kiểm thử mà không cần thiết lập phức tạp
---
### 7.2. Module Test Architect (TEA) — Nâng cao
**Phù hợp với:** Dự án lớn, miền nghiệp vụ phức tạp, cần truy vết yêu cầu
**Cài đặt:**
```bash
npx bmad-method install
# Chọn thêm module: TEA (Test Architect)
```
**Agent TEA:** Murat (Master Test Architect)
**9 workflow của TEA:**
| # | Workflow | Mục đích |
|---|---|---|
| 1 | **Test Design** | Tạo chiến lược kiểm thử gắn với yêu cầu (PRD/AC) |
| 2 | **ATDD** | Phát triển hướng Acceptance Test — viết test trước khi code |
| 3 | **Automate** | Tạo automated test với pattern nâng cao |
| 4 | **Test Review** | Kiểm tra chất lượng và độ bao phủ của bộ test |
| 5 | **Traceability** | Liên kết test ngược về yêu cầu trong PRD |
| 6 | **NFR Assessment** | Đánh giá yêu cầu phi chức năng (performance, security, reliability) |
| 7 | **CI Setup** | Cấu hình thực thi test trong CI/CD pipeline |
| 8 | **Framework Scaffolding** | Dựng hạ tầng test cho dự án mới |
| 9 | **Release Gate** | Ra quyết định go/no-go dựa trên chất lượng |
**Hệ thống ưu tiên P0P3:**
| Mức | Ý nghĩa | Ví dụ |
|---|---|---|
| **P0** | Critical — phải pass 100% | Thanh toán, xác thực, bảo mật |
| **P1** | High — phải pass cho release | Core business flow |
| **P2** | Medium — nên pass | Tính năng phụ, edge cases |
| **P3** | Low — test khi có thể | UI detail, minor UX |
**Luồng ATDD với TEA:**
```
QC viết Acceptance Criteria (AC) →
TEA tạo test từ AC (trước khi code) →
Developer implement để test pass →
TEA verify traceability (AC ↔ test ↔ requirement) →
Release Gate go/no-go
```
---
### 7.3. So sánh hai hướng tiếp cận
| Yếu tố | QA tích hợp sẵn | Module TEA |
|---|---|---|
| Thời điểm test | Sau khi epic hoàn tất | Có thể trước khi code (ATDD) |
| Thiết lập | Không cần cài thêm | Cài module riêng |
| Loại test | API + E2E | API, E2E, ATDD, NFR, Performance |
| Truy vết yêu cầu | Không | Có (Traceability workflow) |
| Release gate | Không | Có (go/no-go) |
| Phù hợp nhất | Dự án nhỏtrung bình | Dự án lớn, có compliance |
---
### 7.4. Vị trí kiểm thử trong vòng đời dự án
```
Story 1: Dev → Code Review → ✓
Story 2: Dev → Code Review → ✓
Story 3: Dev → Code Review → ✓
...
Epic hoàn tất → QA Test Generation → Tests pass → Epic Retrospective
```
> **Lưu ý:** QA Test Generation chạy **sau khi toàn bộ epic hoàn tất**, không phải sau từng story. Mục đích là kiểm thử tích hợp các stories với nhau.
---
### 7.5. Edge Case Hunter — Công cụ tìm trường hợp biên
Ngoài QA workflow, Developer Agent còn hỗ trợ:
```bash
# Trong hội thoại với Developer Agent
bmad-review-edge-case-hunter
```
Phân tích toàn bộ nhánh điều kiện trong code để tìm:
- Trường hợp biên chưa được xử lý
- Null/undefined checks bị thiếu
- Điều kiện race condition
- Input validation gaps
---
## 8. Các công cụ hỗ trợ
### 8.1. Party Mode — Thảo luận đa agent
```bash
bmad-party-mode
```
Triệu tập nhiều agent vào cùng một hội thoại để thảo luận các quyết định quan trọng:
- **Kiến trúc**: PM + Architect + Developer cùng đánh giá trade-off
- **Tính năng phức tạp**: UX Designer + Architect + PM
- **Post-mortem**: Tất cả agent cùng phân tích sự cố
- **Sprint retrospective**: PM + Developer + QC
### 8.2. Advanced Elicitation — Tinh luyện đầu ra
```bash
bmad-advanced-elicitation
```
Buộc AI xem xét lại đầu ra bằng các phương pháp:
| Phương pháp | Mục đích |
|---|---|
| **Pre-mortem** | Giả sử thất bại → lần ngược nguyên nhân |
| **First Principles** | Loại bỏ giả định, bắt đầu từ sự thật cơ bản |
| **Red Team / Blue Team** | Tự tấn công, tự bảo vệ |
| **Socratic Questioning** | Chất vấn mọi khẳng định |
| **Constraint Removal** | Bỏ ràng buộc → thấy giải pháp khác |
| **Stakeholder Mapping** | Đánh giá từ góc nhìn từng bên liên quan |
Dùng sau khi có một tài liệu quan trọng (PRD, Architecture) để tìm điểm yếu trước khi tiếp tục.
### 8.3. Adversarial Review — Review hoài nghi
```bash
bmad-review-adversarial-general
```
Review kiểu "devil's advocate" — giả định vấn đề luôn tồn tại:
- Phải tìm được tối thiểu 10 vấn đề
- Tìm những gì **còn thiếu**, không chỉ những gì sai
- Trực giao với Edge Case Hunter
### 8.4. Distillator — Nén tài liệu cho LLM
```bash
bmad-distillator
```
Khi tài liệu quá lớn (PRD dài, Architecture phức tạp), Distillator nén nội dung tối ưu cho LLM mà không mất thông tin quan trọng.
### 8.5. Shard Large Documents — Tách file lớn
```bash
bmad-shard-doc
```
Tách file markdown lớn thành các file phần nhỏ hơn, với index tự động.
---
## 9. Cấu trúc thư mục dự án
Sau khi cài BMAD và chạy qua các giai đoạn, dự án sẽ có cấu trúc:
```
your-project/
├── _bmad/ # Cấu hình BMAD (không chỉnh sửa thủ công)
│ ├── core/ # Module core
│ └── bmm/ # Modules đã cài (TEA, v.v.)
├── _bmad-output/ # Tất cả artifacts sinh ra
│ ├── project-context.md # Bản hiến pháp kỹ thuật của dự án
│ ├── planning-artifacts/
│ │ ├── product-brief.md # Giai đoạn 1 output
│ │ ├── PRD.md # Giai đoạn 2 output
│ │ ├── ux-spec.md # Giai đoạn 2 output (nếu có)
│ │ ├── architecture.md # Giai đoạn 3 output
│ │ └── epics/ # Giai đoạn 3 output
│ │ ├── epic-1-auth/
│ │ │ ├── story-1-login.md
│ │ │ ├── story-2-register.md
│ │ │ └── story-3-reset-password.md
│ │ └── epic-2-dashboard/
│ └── implementation-artifacts/
│ └── sprint-status.yaml # Tracking sprint
├── .claude/skills/ # Skills cho Claude Code
│ ├── bmad-pm.md
│ ├── bmad-architect.md
│ └── ...
├── docs/ # Tài liệu dự án
│ └── index.md # Overview, goals, architecture notes
└── src/ # Source code dự án
```
---
## 10. Mẹo và Best Practices
### Chat mới cho mỗi workflow
> Luôn bắt đầu một hội thoại mới khi chuyển sang workflow khác.
Mỗi workflow của BMAD thiết kế để chạy trong context rõ ràng. Việc tiếp tục hội thoại cũ có thể gây ra nhiễu context, đặc biệt với các workflow dài.
### Đọc kỹ `project-context.md` trước khi bắt đầu sprint
Tất cả agent developer tự động nạp `project-context.md`. Đảm bảo file này luôn cập nhật với:
- Tech stack và phiên bản chính xác
- Quy tắc implementation quan trọng
- Patterns đang dùng trong codebase
### Kiến trúc là bắt buộc khi có nhiều developer
Nếu nhiều agent (hoặc developer) làm việc song song trên các stories khác nhau, kiến trúc phải được định nghĩa trước. Thiếu kiến trúc → các agent tạo ra code xung đột nhau.
### Dùng bmad-help khi không chắc
```
bmad-help Tôi đang ở đâu trong workflow?
bmad-help Story này nên dùng Quick Flow hay Dev Story?
bmad-help Implementation Readiness check thất bại, làm gì tiếp?
```
### Quick Flow không có nghĩa là không có chất lượng
Quick Dev vẫn có code review, vẫn tạo spec (mini), vẫn yêu cầu người approve kết quả. "Nhanh" ở đây là bỏ overhead lập kế hoạch không cần thiết, không phải bỏ qua chất lượng.
### Customize agent theo nhu cầu team
```yaml
# .customize.yaml
agents:
bmad-agent-dev:
persona: "Senior developer theo hướng TDD, luôn viết test trước"
rules:
- "Mọi function public phải có unit test"
- "Không dùng any trong TypeScript"
```
### Vị trí QA trong workflow
```
❌ Sai: Test sau mỗi story ngay lập tức
✅ Đúng: Test sau khi toàn bộ epic hoàn tất (Dev + Code Review cho tất cả stories)
```
E2E test cần toàn bộ tính năng của epic để test integration. Test sớm hơn sẽ gặp dependency chưa sẵn sàng.
---
## Tài liệu tham khảo
| Tài liệu | Đường dẫn |
|---|---|
| Getting Started | [tutorials/getting-started.md](tutorials/getting-started.md) |
| Danh sách Agents | [reference/agents.md](reference/agents.md) |
| Workflow Map | [reference/workflow-map.md](reference/workflow-map.md) |
| Testing Reference | [reference/testing.md](reference/testing.md) |
| Core Tools | [reference/core-tools.md](reference/core-tools.md) |
| Modules | [reference/modules.md](reference/modules.md) |
| Dự án đã tồn tại | [how-to/established-projects.md](how-to/established-projects.md) |
| Project Context | [explanation/project-context.md](explanation/project-context.md) |
| Quick Dev | [explanation/quick-dev.md](explanation/quick-dev.md) |
| Why Solutioning Matters | [explanation/why-solutioning-matters.md](explanation/why-solutioning-matters.md) |
| Cài đặt BMAD | [how-to/install-bmad.md](how-to/install-bmad.md) |
---
*Tài liệu này được tổng hợp từ bản dịch tiếng Việt của BMAD Method Documentation. Cập nhật lần cuối: 2026-04-15.*

View File

@ -0,0 +1,92 @@
---
title: "Xem trước Checkpoint"
description: Review có người trong vòng lặp với hỗ trợ của LLM, dẫn bạn đi qua thay đổi từ mục đích đến chi tiết
sidebar:
order: 3
---
`bmad-checkpoint-preview` là một workflow review tương tác có người trong vòng lặp với hỗ trợ của LLM. Nó dẫn bạn đi qua một thay đổi mã nguồn, từ mục đích và bối cảnh đến các chi tiết quan trọng, để bạn có thể quyết định có nên phát hành, làm lại, hay đào sâu thêm.
![Sơ đồ workflow Checkpoint Preview](/diagrams/checkpoint-preview-diagram.png)
## Luồng điển hình
Bạn chạy `bmad-quick-dev`. Nó làm rõ ý định của bạn, dựng spec, triển khai thay đổi, rồi khi xong sẽ nối thêm một review trail vào file spec và mở file đó trong editor. Bạn nhìn vào spec và thấy thay đổi này chạm tới 20 file, trải trên nhiều module.
Bạn có thể tự liếc diff. Nhưng khoảng 20 file là lúc cách đó bắt đầu kém hiệu quả: bạn mất mạch, bỏ sót liên hệ giữa hai thay đổi ở xa nhau, hoặc duyệt một thứ mà bạn chưa thực sự hiểu. Thay vì vậy, bạn nói "checkpoint" và LLM sẽ dẫn bạn đi qua thay đổi.
Điểm bàn giao đó, từ triển khai tự động quay lại phán đoán của con người, chính là tình huống sử dụng chính. Quick-dev có thể chạy khá lâu với rất ít giám sát. Checkpoint Preview là nơi bạn cầm lại tay lái.
## Vì sao nó tồn tại
Code review có hai kiểu thất bại. Kiểu đầu là người review lướt qua diff, không thấy gì nổi bật và bấm duyệt. Kiểu thứ hai là họ đọc rất kỹ từng file nhưng lại mất mạch tổng thể, thấy từng cái cây mà bỏ lỡ cả khu rừng. Cả hai đều dẫn tới cùng một kết quả: lần review đã không bắt được điều thực sự quan trọng.
Vấn đề cốt lõi nằm ở thứ tự tiếp nhận. Một raw diff trình bày thay đổi theo thứ tự file, gần như không bao giờ là thứ tự giúp xây dựng hiểu biết. Bạn thấy một helper function trước khi biết vì sao nó tồn tại. Bạn thấy một schema change trước khi hiểu tính năng nào đang dùng nó. Người review phải tự dựng lại ý đồ của tác giả từ những manh mối rời rạc, và chính ở bước dựng lại đó sự tập trung thường bị đứt.
Checkpoint Preview giải quyết việc này bằng cách để LLM làm phần dựng lại. Nó đọc diff, spec nếu có, và codebase xung quanh, rồi trình bày thay đổi theo một thứ tự phục vụ việc hiểu, chứ không theo `git diff`.
## Nó hoạt động như thế nào
Workflow này có năm bước. Mỗi bước xây trên bước trước, dần dần chuyển từ "đây là gì?" sang "chúng ta có nên phát hành nó không?"
### 1. Định hướng
Workflow xác định thay đổi đó là gì, từ PR, commit, branch, file spec, hoặc trạng thái git hiện tại, rồi tạo một câu tóm tắt ý định và vài số liệu bề mặt: số file thay đổi, số module bị chạm tới, số dòng logic, số lần băng qua ranh giới, và các public interface mới.
Đây là khoảnh khắc "đúng là thứ tôi đang nghĩ tới chứ?". Trước khi đọc mã, người review xác nhận mình đang nhìn đúng thay đổi và cân chỉnh kỳ vọng về phạm vi.
### 2. Dẫn giải thay đổi (Walkthrough)
Thay đổi được tổ chức theo **mối quan tâm** như validation đầu vào hay API contract, thay vì theo file. Mỗi mối quan tâm có một giải thích ngắn về *vì sao* cách tiếp cận này được chọn, kèm theo các điểm dừng `path:line` có thể bấm để người review đi theo xuyên suốt code.
Đây là bước dùng phán đoán về thiết kế. Người review đánh giá xem hướng tiếp cận có đúng với hệ thống hay không, chứ chưa phải xem code có chính xác tuyệt đối hay không. Các mối quan tâm được sắp từ trên xuống: ý định cấp cao trước, phần triển khai hỗ trợ sau. Người review sẽ không gặp tham chiếu tới thứ mà họ chưa thấy.
### 3. Soi chi tiết
Sau khi người review đã hiểu thiết kế, workflow sẽ đưa ra 2 đến 5 điểm mà nếu sai thì hậu quả lan rộng nhất. Chúng được gắn nhãn theo loại rủi ro như `[auth]`, `[schema]`, `[billing]`, `[public API]`, `[security]` và các nhãn khác, đồng thời được sắp theo mức độ thiệt hại nếu sai.
Đây không phải là một cuộc săn bug. Tính đúng đắn được CI và test tự động lo phần lớn. Bước soi chi tiết nhằm kích hoạt ý thức về rủi ro: "đây là những chỗ mà nếu sai thì cái giá phải trả cao nhất". Nếu muốn đào sâu một khu vực cụ thể, bạn có thể nói "đào sâu vào [khu vực]" để chạy một lần review lại tập trung vào tính đúng đắn.
Nếu spec trước đó đã đi qua các vòng adversarial review, các phát hiện liên quan cũng được đưa ra ở đây. Không phải các bug đã được sửa, mà là những quyết định mà vòng review đó từng gắn cờ để người review hiện tại biết.
### 4. Kiểm thử
Workflow gợi ý 2 đến 5 cách quan sát thủ công để thấy thay đổi thực sự hoạt động. Không phải lệnh test tự động, mà là các quan sát tay giúp tăng niềm tin theo cách test suite không cho bạn được. Một tương tác UI để thử, một lệnh CLI để chạy, một request API để gửi, kèm kết quả kỳ vọng cho từng mục.
Nếu thay đổi không có hành vi nào nhìn thấy được từ phía người dùng, workflow sẽ nói thẳng như vậy. Không bịa thêm việc cho có.
### 5. Kết thúc
Người review đưa ra quyết định: duyệt, làm lại, hay tiếp tục thảo luận. Nếu đang duyệt PR, workflow có thể hỗ trợ với `gh pr review --approve`. Nếu cần làm lại, nó sẽ giúp chẩn đoán vấn đề nằm ở cách tiếp cận, spec, hay phần triển khai, đồng thời hỗ trợ soạn phản hồi có thể hành động được và gắn với vị trí code cụ thể.
## Đây là một cuộc hội thoại, không phải bản báo cáo
Workflow trình bày từng bước như một điểm khởi đầu, không phải lời kết luận cuối cùng. Giữa các bước, hoặc ngay giữa một bước, bạn có thể trao đổi với LLM, hỏi thêm, phản biện cách nó đóng khung vấn đề, hoặc kéo thêm skill khác để lấy một góc nhìn khác:
- **"run advanced elicitation on the error handling"** - ép LLM xem xét lại và tinh chỉnh phân tích cho một khu vực cụ thể
- **"party mode on whether this schema migration is safe"** - kéo nhiều góc nhìn agent vào một cuộc tranh luận tập trung
- **"run code review"** - tạo ra các phát hiện có cấu trúc với phân tích đối kháng và edge case
Workflow checkpoint không khóa bạn vào một đường đi tuyến tính. Nó cho bạn cấu trúc khi bạn cần, và tránh cản đường khi bạn muốn tự khám phá. Năm bước ở đây để bảo đảm bạn nhìn được toàn cảnh, còn việc đi sâu đến mức nào ở mỗi bước và gọi thêm công cụ nào hoàn toàn là do bạn quyết định.
## Lộ trình review (Review Trail)
Bước dẫn giải thay đổi hoạt động tốt nhất khi nó có một **thứ tự review gợi ý (Suggested Review Order)**, tức một danh sách các điểm dừng do tác giả spec viết ra để dẫn người review đi qua thay đổi. Nếu spec có phần này, workflow sẽ dùng trực tiếp.
Nếu không có review trail do tác giả tạo, workflow sẽ tự sinh một trail từ diff và bối cảnh codebase. Trail do máy sinh ra vẫn kém hơn trail do tác giả viết, nhưng vẫn tốt hơn rất nhiều so với việc đọc thay đổi theo thứ tự file.
## Khi nào nên dùng
Tình huống chính là bước bàn giao sau `bmad-quick-dev`: phần triển khai đã xong, file spec đang mở trong editor với review trail đã được nối thêm, và bạn cần quyết định có nên phát hành hay không. Lúc đó chỉ cần nói "checkpoint" là bắt đầu.
Nó cũng hoạt động độc lập:
- **Review một PR** - đặc biệt hữu ích khi PR có nhiều hơn vài file hoặc có thay đổi cắt ngang nhiều khu vực
- **Làm quen với một thay đổi (onboard to a change)** - khi bạn cần hiểu chuyện gì đã xảy ra trên một branch mà bạn không phải người viết
- **Review sprint (sprint review)** - workflow có thể nhặt các story được đánh dấu `review` trong file trạng thái sprint của bạn
Bạn có thể gọi nó bằng cách nói "checkpoint" hoặc "dẫn tôi đi qua thay đổi này". Nó chạy được trong mọi terminal, nhưng sẽ phát huy tốt nhất trong IDE như VS Code, Cursor hoặc công cụ tương tự, vì workflow tạo tham chiếu `path:line` ở mọi bước. Trong terminal tích hợp của IDE, các tham chiếu đó có thể bấm được, nên bạn có thể nhảy qua lại giữa các file khi đi theo review trail.
## Nó không phải là gì
Checkpoint Preview không thay thế review tự động. Nó không chạy linter, type checker, hay test suite. Nó không chấm mức độ nghiêm trọng hay đưa ra kết luận pass/fail. Nó là một bản hướng dẫn đọc để giúp con người áp dụng phán đoán của mình vào đúng những chỗ đáng chú ý nhất.

View File

@ -0,0 +1,94 @@
---
title: "Agent có tên riêng (Named Agents)"
description: Vì sao các agent của BMad có tên, persona và bề mặt tùy chỉnh riêng, và điều đó mở khóa điều gì so với cách tiếp cận dựa trên menu hoặc prompt trống
sidebar:
order: 1
---
Bạn nói: "Hey Mary, brainstorm với tôi nhé", và Mary được kích hoạt. Cô ấy chào bạn theo tên, bằng ngôn ngữ bạn đã cấu hình, với persona đặc trưng của riêng mình. Cô ấy nhắc rằng `bmad-help` luôn sẵn sàng. Rồi cô ấy bỏ qua menu và đi thẳng vào brainstorming vì ý định của bạn đã đủ rõ.
Trang này giải thích điều gì thực sự đang diễn ra và vì sao BMad được thiết kế theo cách đó.
## Chiếc ghế ba chân
Mô hình agent của BMad đứng trên ba primitive kết hợp với nhau:
| Thành phần nền (primitive) | Nó cung cấp gì | Nó nằm ở đâu |
|---|---|---|
| **Skill** | Năng lực, tức một việc rời rạc mà assistant có thể làm như brainstorming, viết PRD hay triển khai story | `.claude/skills/{skill-name}/SKILL.md` hoặc vị trí tương đương theo IDE |
| **Named agent** | Tính liên tục của persona, tức một danh tính dễ nhận ra bọc quanh một nhóm skill có cùng giọng điệu, nguyên tắc và dấu hiệu nhận biết | Các skill có thư mục bắt đầu bằng `bmad-agent-*` |
| **Customization** | Khả năng biến nó thành của riêng bạn: override để đổi hành vi của agent, thêm tích hợp MCP, thay template, chồng convention của tổ chức | `_bmad/custom/{skill-name}.toml` cho team và `.user.toml` cho cá nhân |
Chỉ cần bỏ đi một chân là trải nghiệm sẽ sụp:
- Skill mà không có agent sẽ thành danh sách khả năng mà người dùng phải tự nhớ tên hoặc mã
- Agent mà không có skill sẽ chỉ là persona không có gì để làm
- Không có customization thì mọi người đều nhận cùng một hành vi mặc định, và muốn thêm convention nội bộ là phải fork
## Named agents mang lại điều gì
BMad hiện có sáu named agent, mỗi agent gắn với một phase trong BMad Method:
| Agent | Phase | Module |
|---|---|---|
| 📊 **Mary**, Chuyên viên phân tích nghiệp vụ (Business Analyst) | Analysis | market research, brainstorming, product briefs, PRFAQs |
| 📚 **Paige**, Technical Writer | Analysis | project documentation, diagrams, doc validation |
| 📋 **John**, Quản lý sản phẩm (Product Manager) | Planning | PRD creation, epic/story breakdown, implementation readiness |
| 🎨 **Sally**, Nhà thiết kế UX (UX Designer) | Planning | UX design specifications |
| 🏗️ **Winston**, Kiến trúc sư hệ thống (System Architect) | Solutioning | technical architecture, alignment checks |
| 💻 **Amelia**, Kỹ sư cấp cao (Senior Engineer) | Implementation | story execution, quick-dev, code review, sprint planning |
Mỗi agent có một danh tính hardcode gồm tên, chức danh, domain, và một lớp có thể tùy chỉnh gồm vai trò, nguyên tắc, phong cách giao tiếp, icon và menu. Bạn có thể viết lại nguyên tắc của Mary hoặc thêm menu item cho cô ấy, nhưng bạn không thể đổi tên cô ấy. Đó là chủ ý thiết kế. Nhận diện thương hiệu của agent phải sống sót qua lớp tùy chỉnh để câu "hey Mary" luôn kích hoạt đúng analyst, bất kể team đã nắn hành vi của cô ấy theo cách nào.
## Luồng kích hoạt
Khi bạn gọi một named agent, tám bước sau sẽ chạy theo thứ tự:
1. **Resolve cấu hình agent**: merge `customize.toml` gốc với override của team và cá nhân qua một Python resolver dùng `tomllib`
2. **Chạy các bước tiền xử lý (prepend steps)**: mọi hành vi pre-flight mà team đã cấu hình
3. **Nhập persona**: danh tính hardcode cộng với vai trò, phong cách giao tiếp và nguyên tắc đã tùy chỉnh
4. **Nạp persistent facts**: quy tắc tổ chức, ghi chú compliance, hoặc cả file được nạp qua tiền tố `file:`
5. **Nạp config**: tên người dùng, ngôn ngữ giao tiếp, ngôn ngữ đầu ra, đường dẫn artifact
6. **Chào người dùng**: lời chào cá nhân hóa, đúng ngôn ngữ cấu hình và có emoji prefix của agent để bạn nhìn là biết ai đang nói
7. **Chạy các bước hậu xử lý (append steps)**: mọi bước thiết lập sau lời chào mà team đã cấu hình
8. **Dispatch hoặc hiện menu**: nếu tin nhắn mở đầu của bạn khớp một menu item thì agent đi thẳng vào đó, nếu không thì hiện menu và chờ input
Bước 8 là nơi ý định gặp năng lực. Câu "Hey Mary, brainstorm với tôi nhé" bỏ qua phần render menu vì `bmad-brainstorming` là một mapping quá rõ với mục `BP` trong menu của Mary. Nếu bạn nói mơ hồ, cô ấy chỉ hỏi lại một lần, ngắn gọn, chứ không biến xác nhận thành nghi thức. Nếu chẳng có mục nào phù hợp, cô ấy tiếp tục cuộc hội thoại như bình thường.
## Vì sao không chỉ dùng menu
Menu buộc người dùng phải chủ động học công cụ. Bạn phải nhớ brainstorming nằm dưới mã `BP` của analyst chứ không phải PM, và phải nhớ persona nào sở hữu nhóm khả năng nào. Toàn bộ gánh nặng nhận thức đó do công cụ đẩy sang cho người dùng.
Named agents đảo ngược điều đó. Bạn chỉ cần nói điều mình muốn, với đúng người mình nghĩ tới, bằng ngôn từ tự nhiên. Agent biết họ là ai và họ làm gì. Khi ý định của bạn đủ rõ, họ chỉ việc bắt đầu.
Menu vẫn còn đó như một phương án dự phòng, hiện ra khi bạn đang khám phá, và biến mất khi bạn không cần nó.
## Vì sao không chỉ dùng prompt trống
Prompt trống giả định rằng bạn biết "câu thần chú". "Giúp tôi brainstorm" có thể hiệu quả, nhưng "hãy ideate giúp tôi một ý tưởng SaaS" có thể cho kết quả khác, và đầu ra phụ thuộc khá nhiều vào cách bạn diễn đạt. Khi đó người dùng gần như phải kiêm luôn vai trò kỹ sư prompt (prompt engineer).
Named agents thêm cấu trúc mà không đóng mất sự tự do. Persona giữ ổn định, năng lực thì dễ khám phá, và `bmad-help` luôn chỉ cách bạn một lệnh. Bạn không phải đoán agent làm được gì, nhưng cũng không cần học thuộc một cuốn manual để dùng nó.
## Tùy chỉnh là công dân hạng nhất
Chính mô hình customization làm cho cách tiếp cận này mở rộng được ra ngoài phạm vi của một lập trình viên đơn lẻ.
Mỗi agent đi kèm một `customize.toml` với mặc định hợp lý. Team có thể commit override vào `_bmad/custom/bmad-agent-{role}.toml`. Mỗi cá nhân có thể chồng thêm sở thích riêng trong `.user.toml` bị gitignore. Resolver sẽ merge cả ba lớp tại thời điểm kích hoạt theo các quy tắc có tính dự đoán.
Đa số người dùng không cần tự tay viết các file đó. Skill `bmad-customize` sẽ dẫn họ qua việc chọn đúng mục tiêu, quyết định override ở mức agent hay workflow, viết file và xác minh merge. Nhờ vậy bề mặt tùy chỉnh vẫn tiếp cận được với bất cứ ai hiểu ý định của mình, chứ không chỉ người rành TOML.
Ví dụ cụ thể: một team commit một file yêu cầu Amelia luôn dùng Context7 MCP tool khi tra tài liệu thư viện, và fallback sang Linear nếu story không xuất hiện trong danh sách epic cục bộ. Từ đó mọi dev workflow mà Amelia dispatch như `dev-story`, `quick-dev`, `create-story`, `code-review` đều tự động thừa hưởng hành vi này mà không cần sửa source hay lặp lại cấu hình từng workflow.
Ngoài ra còn có một bề mặt tùy chỉnh thứ hai cho các mối quan tâm *xuyên suốt*: `_bmad/config.toml`, `_bmad/config.user.toml`, `_bmad/custom/config.toml``_bmad/custom/config.user.toml`. Đây là nơi **agent roster** sống, tức các descriptor gọn nhẹ mà những skill như `bmad-party-mode`, `bmad-retrospective``bmad-advanced-elicitation` dùng để biết ai có mặt và phải nhập vai họ thế nào. Bạn có thể rebrand một agent cho cả tổ chức bằng team override, hoặc thêm những giọng hư cấu như Kirk, Spock hay một persona chuyên gia domain qua `.user.toml`, tất cả mà không cần đụng vào thư mục skill. File per-skill quyết định Mary *hành xử* như thế nào khi cô ấy kích hoạt; cấu hình trung tâm quyết định các skill khác *nhìn thấy* cô ấy ra sao khi quan sát toàn bộ đội hình.
Để xem toàn bộ bề mặt tùy chỉnh và ví dụ thực tế:
- [Cách tùy chỉnh BMad](../how-to/customize-bmad.md): tài liệu tham chiếu cho những gì có thể tùy chỉnh và merge diễn ra thế nào
- [Cách mở rộng BMad cho tổ chức của bạn](../how-to/expand-bmad-for-your-org.md): năm recipe hoàn chỉnh trải từ quy tắc ở cấp agent, convention workflow, publish ra hệ thống ngoài, thay template đầu ra đến tùy chỉnh roster agent
- Skill `bmad-customize`: trợ lý soạn cấu hình (authoring helper) có hướng dẫn để biến ý định thành một file override đúng chỗ và đã được kiểm chứng
## Ý tưởng lớn hơn phía sau
Hầu hết các trợ lý AI (AI assistant) ngày nay hoặc là menu, hoặc là prompt, và cả hai đều chuyển phần gánh nặng nhận thức sang người dùng. Agent có tên riêng kết hợp với skill có thể tùy chỉnh cho phép bạn trò chuyện với một đồng đội đã hiểu công việc, đồng thời cho phép tổ chức của bạn nắn đồng đội đó theo nhu cầu mà không cần fork.
Lần tới khi bạn gõ "Hey Mary, brainstorm với tôi nhé" và cô ấy chỉ việc bắt tay vào làm, hãy để ý thứ đã *không* xảy ra. Không có slash command. Không có menu phải điều hướng. Không có lời nhắc gượng gạo về những gì cô ấy có thể làm. Chính sự vắng mặt đó mới là thiết kế.

View File

@ -1,171 +1,395 @@
---
title: "Cách tùy chỉnh BMad"
description: Tùy chỉnh agent, workflow và module trong khi vẫn giữ khả năng tương thích khi cập nhật
title: 'Cách tùy chỉnh BMad'
description: Tùy chỉnh agent và workflow trong khi vẫn giữ khả năng tương thích khi cập nhật
sidebar:
order: 7
order: 8
---
Sử dụng các tệp `.customize.yaml` để điều chỉnh hành vi, persona và menu của agent, đồng thời giữ lại thay đổi của bạn qua các lần cập nhật.
Điều chỉnh persona của agent, chèn ngữ cảnh theo domain, thêm khả năng mới và cấu hình hành vi workflow mà không cần sửa các file đã cài. Các tùy chỉnh của bạn sẽ được giữ nguyên qua mọi lần cập nhật.
:::tip[Không muốn tự viết TOML? Hãy dùng `bmad-customize`]
Skill `bmad-customize` là trợ lý tạo cấu hình có hướng dẫn cho **bề mặt override agent/workflow theo từng skill** được mô tả trong tài liệu này. Nó quét những gì có thể tùy chỉnh trong bản cài đặt của bạn, giúp bạn chọn đúng bề mặt (agent hay workflow), ghi file override và xác minh merge đã áp dụng. Override ở mức cấu hình trung tâm (`_bmad/custom/config.toml`) chưa nằm trong phạm vi v1, nên phần đó vẫn cần viết tay theo mục Cấu hình trung tâm bên dưới. Hãy chạy skill này khi bạn muốn thay đổi theo từng skill; tài liệu này là phần tham chiếu cho *có thể tùy chỉnh gì* và merge hoạt động ra sao.
:::
## Khi nào nên dùng
- Bạn muốn thay đổi tên, tính cách hoặc phong cách giao tiếp của một agent
- Bạn cần agent ghi nhớ bối cảnh riêng của dự án
- Bạn muốn thêm các mục menu tùy chỉnh để kích hoạt workflow hoặc prompt của riêng mình
- Bạn muốn agent luôn thực hiện một số hành động cụ thể mỗi khi khởi động
- Bạn muốn thay đổi tính cách hoặc phong cách giao tiếp của agent
- Bạn cần cung cấp cho agent các "persistent facts" để luôn nhớ, ví dụ "tổ chức của chúng tôi chỉ dùng AWS"
- Bạn muốn thêm các bước khởi động có tính thủ tục mà agent phải chạy mỗi phiên
- Bạn muốn thêm menu item tùy chỉnh để gọi skill hoặc prompt riêng
- Team của bạn cần các tùy chỉnh dùng chung được commit vào git, đồng thời vẫn cho phép mỗi cá nhân chồng thêm sở thích riêng
:::note[Điều kiện tiên quyết]
- BMad đã được cài trong dự án của bạn (xem [Cách cài đặt BMad](./install-bmad.md))
- Trình soạn thảo văn bản để chỉnh sửa tệp YAML
- Python 3.11+ có trên PATH của bạn (để chạy resolver; dùng stdlib `tomllib`, không cần `pip install`, `uv` hay virtualenv)
- Một trình soạn thảo văn bản cho file TOML
:::
:::caution[Giữ an toàn cho các tùy chỉnh của bạn]
Luôn sử dụng các tệp `.customize.yaml` được mô tả trong tài liệu này thay vì sửa trực tiếp tệp agent. Trình cài đặt sẽ ghi đè các tệp agent khi cập nhật, nhưng vẫn giữ nguyên các thay đổi trong `.customize.yaml`.
:::
## Cách hoạt động
Mỗi skill có thể tùy chỉnh đều đi kèm một file `customize.toml` chứa cấu hình mặc định. File này định nghĩa toàn bộ bề mặt tùy chỉnh của skill, nên hãy đọc nó để biết có thể chỉnh gì. Bạn **không bao giờ** sửa trực tiếp file này. Thay vào đó, bạn tạo các file override dạng thưa, chỉ chứa những trường bạn muốn đổi.
### Mô hình override ba lớp
```text
Ưu tiên 1 (thắng): _bmad/custom/{skill-name}.user.toml (cá nhân, bị gitignore)
Ưu tiên 2: _bmad/custom/{skill-name}.toml (team/tổ chức, được commit)
Ưu tiên 3 (gốc): customize.toml của chính skill (mặc định)
```
Thư mục `_bmad/custom/` ban đầu là rỗng. File chỉ xuất hiện khi ai đó thực sự bắt đầu tùy chỉnh.
### Quy tắc merge theo hình dạng, không theo tên trường
Resolver áp dụng bốn quy tắc cấu trúc. Tên trường không được hardcode riêng; hành vi hoàn toàn được quyết định bởi dạng dữ liệu:
| Dạng | Quy tắc |
|---|---|
| Scalar (string, int, bool, float) | Giá trị override sẽ thắng |
| Table | Deep merge, tức merge đệ quy theo các quy tắc này |
| Mảng các table mà mọi phần tử đều dùng cùng **một** trường định danh (`code` ở tất cả phần tử, hoặc `id` ở tất cả phần tử) | Merge theo khóa đó, phần tử trùng khóa sẽ **thay tại chỗ**, phần tử mới sẽ **append** |
| Mọi mảng khác (mảng scalar, table không có định danh, hoặc trộn `code``id`) | **Append**: phần tử gốc trước, rồi team, rồi user |
**Không có cơ chế xóa.** Override không thể xóa phần tử mặc định. Nếu bạn cần vô hiệu hóa một menu item mặc định, hãy override nó theo `code` bằng mô tả hoặc prompt no-op. Nếu cần tái cấu trúc mảng sâu hơn, bạn phải fork skill.
**Quy ước `code` / `id`.** BMad dùng `code` (định danh ngắn như `"BP"` hoặc `"R1"`) và `id` (định danh ổn định dài hơn) làm merge key cho mảng các table. Nếu bạn tự tạo một mảng table muốn có khả năng replace-by-key thay vì append-only, hãy chọn **một** quy ước duy nhất và dùng nhất quán cho toàn bộ mảng. Nếu trộn `code` ở phần tử này và `id` ở phần tử khác, resolver sẽ rơi về chế độ append vì nó không đoán merge theo khóa nào.
### Một số trường của agent là chỉ đọc
`agent.name``agent.title` vẫn nằm trong `customize.toml` như metadata nguồn gốc, nhưng `SKILL.md` của agent không đọc hai trường này ở runtime, vì danh tính của agent được hardcode. Bạn đặt `name = "Bob"` trong file override cũng sẽ không có tác dụng. Nếu bạn thật sự cần một agent với tên khác, hãy copy thư mục skill, đổi tên và phát hành nó như một custom skill.
## Các bước thực hiện
### 1. Xác định vị trí các tệp tùy chỉnh
### 1. Tìm bề mặt tùy chỉnh của skill
Sau khi cài đặt, bạn sẽ tìm thấy một tệp `.customize.yaml` cho mỗi agent tại:
Hãy mở file `customize.toml` trong thư mục skill đã được cài. Ví dụ với PM agent:
```text
_bmad/_config/agents/
├── core-bmad-master.customize.yaml
├── bmm-dev.customize.yaml
├── bmm-pm.customize.yaml
└── ... (một tệp cho mỗi agent đã cài)
.claude/skills/bmad-agent-pm/customize.toml
```
### 2. Chỉnh sửa tệp tùy chỉnh
(Đường dẫn cụ thể thay đổi theo IDE: Cursor dùng `.cursor/skills/`, Cline dùng `.cline/skills/`, v.v.)
Mở tệp `.customize.yaml` của agent mà bạn muốn sửa. Mỗi phần đều là tùy chọn, chỉ tùy chỉnh những gì bạn cần.
Đây là schema chính thức. Mọi trường bạn nhìn thấy trong file này đều có thể tùy chỉnh, ngoại trừ các trường danh tính chỉ đọc đã nêu ở trên.
| Phần | Cách hoạt động | Mục đích |
| --- | --- | --- |
| `agent.metadata` | Thay thế | Ghi đè tên hiển thị của agent |
| `persona` | Thay thế | Đặt vai trò, danh tính, phong cách và các nguyên tắc |
| `memories` | Nối thêm | Thêm bối cảnh cố định mà agent luôn ghi nhớ |
| `menu` | Nối thêm | Thêm mục menu tùy chỉnh cho workflow hoặc prompt |
| `critical_actions` | Nối thêm | Định nghĩa hướng dẫn khởi động cho agent |
| `prompts` | Nối thêm | Tạo các prompt tái sử dụng cho các hành động trong menu |
### 2. Tạo file override của bạn
Những phần được đánh dấu **Thay thế** sẽ ghi đè hoàn toàn cấu hình mặc định của agent. Những phần được đánh dấu **Nối thêm** sẽ bổ sung vào cấu hình hiện có.
Tạo thư mục `_bmad/custom/` ở root dự án nếu nó chưa tồn tại. Sau đó tạo file đặt theo tên skill:
**Tên agent**
Thay đổi cách agent tự giới thiệu:
```yaml
agent:
metadata:
name: 'Spongebob' # Mặc định: "Amelia"
```text
_bmad/custom/
bmad-agent-pm.toml # override của team (commit vào git)
bmad-agent-pm.user.toml # sở thích cá nhân (gitignore)
```
**Persona**
:::caution[KHÔNG copy nguyên file `customize.toml`]
File override phải **thưa**. Chỉ đưa vào những trường bạn thực sự muốn đổi, không hơn.
Thay thế tính cách, vai trò và phong cách giao tiếp của agent:
Mọi trường bạn bỏ qua sẽ tự động được kế thừa từ lớp bên dưới. Nếu bạn copy toàn bộ `customize.toml` vào file override, những bản cập nhật sau này sẽ không chảy vào các giá trị mặc định mới nữa và bạn sẽ âm thầm bị lệch qua mỗi release.
:::
```yaml
persona:
role: 'Senior Full-Stack Engineer'
identity: 'Sống trong quả dứa (dưới đáy biển)'
communication_style: 'Spongebob gây phiền'
principles:
- 'Không lồng quá sâu, dev Spongebob ghét nesting quá 2 cấp'
- 'Ưu tiên composition hơn inheritance'
**Ví dụ: đổi icon và thêm một principle**
```toml
# _bmad/custom/bmad-agent-pm.toml
# Chỉ ghi những trường cần đổi. Phần còn lại vẫn kế thừa.
[agent]
icon = "🏥"
principles = [
"Không phát hành bất cứ thứ gì không thể vượt qua kiểm toán của FDA.",
]
```
Phần `persona` sẽ thay thế toàn bộ persona mặc định, vì vậy nếu đặt phần này bạn nên cung cấp đầy đủ cả bốn trường.
Ví dụ này append thêm principle mới vào danh sách mặc định và thay icon. Mọi trường khác vẫn giữ nguyên như bản gốc.
**Memories**
### 3. Tùy chỉnh đúng phần bạn cần
Thêm bối cảnh cố định mà agent sẽ luôn nhớ:
Mọi ví dụ bên dưới đều giả định schema agent phẳng của BMad. Các trường nằm trực tiếp trong `[agent]`, không có các sub-table như `metadata` hay `persona`.
```yaml
memories:
- 'Làm việc tại Krusty Krab'
- 'Người nổi tiếng yêu thích: David Hasselhoff'
- 'Đã học ở Epic 1 rằng giả vờ test đã pass là không ổn'
**Scalar (`icon`, `role`, `identity`, `communication_style`).** Scalar override sẽ thắng, nên bạn chỉ cần đặt những trường đang muốn đổi:
```toml
# _bmad/custom/bmad-agent-pm.toml
[agent]
icon = "🏥"
role = "Dẫn dắt product discovery cho domain healthcare có ràng buộc pháp lý."
communication_style = "Chính xác, nhạy với compliance, đặt các câu hỏi mang hình dạng kiểm soát ngay từ sớm."
```
**Mục menu**
**Persistent facts, principles, activation hooks (các mảng append).** Bốn mảng dưới đây đều là append-only. Phần tử của team được thêm sau mặc định, phần tử user được thêm cuối cùng.
Thêm các mục tùy chỉnh vào menu hiển thị của agent. Mỗi mục cần có `trigger`, đích đến (`workflow` hoặc `action`) và `description`:
```toml
[agent]
# Các fact tĩnh mà agent luôn giữ trong đầu trong cả phiên: quy tắc tổ chức,
# hằng số domain, sở thích của người dùng. Khác với runtime memory sidecar.
#
# Mỗi mục có thể là một câu literal, hoặc tham chiếu `file:` để nạp nội dung
# file làm facts (hỗ trợ cả glob).
persistent_facts = [
"Tổ chức của chúng tôi chỉ dùng AWS, không đề xuất GCP hay Azure.",
"Mọi PRD đều phải có legal sign-off trước khi engineering kickoff.",
"Người dùng mục tiêu là bác sĩ lâm sàng, không phải bệnh nhân, nên ví dụ phải bám theo đối tượng đó.",
"file:{project-root}/docs/compliance/hipaa-overview.md",
"file:{project-root}/_bmad/custom/company-glossary.md",
]
```yaml
menu:
- trigger: my-workflow
workflow: 'my-custom/workflows/my-workflow.yaml'
description: Workflow tùy chỉnh của tôi
- trigger: deploy
action: '#deploy-prompt'
description: Triển khai lên production
# Thêm vào hệ giá trị của agent
principles = [
"Không phát hành bất cứ thứ gì không thể vượt qua kiểm toán của FDA.",
"Giá trị người dùng là trước hết, compliance là luôn luôn.",
]
# Chạy TRƯỚC activation tiêu chuẩn (persona, persistent_facts, config, greet).
# Dùng cho pre-flight load, compliance checks, hoặc thứ gì cần có sẵn trong
# context trước khi agent tự giới thiệu.
activation_steps_prepend = [
"Quét {project-root}/docs/compliance/ và nạp mọi tài liệu liên quan HIPAA vào context.",
]
# Chạy SAU khi greet, TRƯỚC menu. Dùng cho thiết lập nặng về context mà bạn
# muốn chạy sau khi người dùng đã được chào.
activation_steps_append = [
"Đọc {project-root}/_bmad/custom/company-glossary.md nếu file tồn tại.",
]
```
**Critical Actions**
**Hai hook này có vai trò khác nhau.** `prepend` chạy trước lời chào để agent có thể nạp ngữ cảnh cần thiết ngay cả khi cá nhân hóa lời chào. `append` chạy sau lời chào để người dùng không phải nhìn màn hình trống trong lúc agent quét một lượng lớn context.
Định nghĩa các hướng dẫn sẽ chạy khi agent khởi động:
**Tùy chỉnh menu (merge theo `code`).** Menu là một mảng table. Mỗi item có trường `code`, nên resolver merge theo mã này: item có `code` trùng sẽ thay tại chỗ, item mới sẽ được append.
```yaml
critical_actions:
- 'Kiểm tra pipeline CI bằng XYZ Skill và cảnh báo người dùng ngay khi khởi động nếu có việc khẩn cấp cần xử lý'
Với TOML array-of-tables, mỗi item dùng cú pháp `[[agent.menu]]`:
```toml
# Thay item CE hiện có bằng một custom skill
[[agent.menu]]
code = "CE"
description = "Tạo Epic theo framework delivery của tổ chức"
skill = "custom-create-epics"
# Thêm item mới (RC chưa tồn tại trong mặc định)
[[agent.menu]]
code = "RC"
description = "Chạy compliance pre-check"
prompt = """
Đọc {project-root}/_bmad/custom/compliance-checklist.md
và quét toàn bộ tài liệu trong {planning_artifacts} theo checklist đó.
Báo cáo mọi khoảng trống và trích dẫn điều khoản quy định tương ứng.
"""
```
**Prompt tùy chỉnh**
Mỗi menu item chỉ có đúng một trong hai trường `skill` hoặc `prompt`. Những item không xuất hiện trong file override của bạn sẽ giữ nguyên mặc định.
Tạo các prompt tái sử dụng để mục menu có thể tham chiếu bằng `action="#id"`:
**Tham chiếu file.** Khi một trường văn bản cần trỏ tới file (trong `persistent_facts`, `activation_steps_prepend`, `activation_steps_append`, hoặc `prompt` của menu item), hãy dùng đường dẫn đầy đủ dựa trên `{project-root}`. Dù file nằm cạnh override trong `_bmad/custom/`, bạn vẫn nên viết rõ là `{project-root}/_bmad/custom/info.md`. Agent sẽ resolve `{project-root}` ở runtime.
```yaml
prompts:
- id: deploy-prompt
content: |
Triển khai nhánh hiện tại lên production:
1. Chạy toàn bộ test
2. Build dự án
3. Thực thi script triển khai
### 4. Cá nhân và team
**File của team** (`bmad-agent-pm.toml`): commit vào git, áp dụng cho cả tổ chức. Dùng cho compliance rules, company persona, năng lực tùy chỉnh dùng chung.
**File cá nhân** (`bmad-agent-pm.user.toml`): tự động bị gitignore. Dùng cho điều chỉnh giọng điệu, sở thích workflow cá nhân và các fact riêng mà agent cần lưu ý cho riêng bạn.
```toml
# _bmad/custom/bmad-agent-pm.user.toml
[agent]
persistent_facts = [
"Khi trình bày phương án, luôn kèm ước lượng độ phức tạp ở mức thô (low/medium/high).",
]
```
### 3. Áp dụng thay đổi
## Cách quá trình resolve diễn ra
Sau khi chỉnh sửa, cài đặt lại để áp dụng thay đổi:
Khi agent được kích hoạt, `SKILL.md` của nó sẽ gọi một shared Python script để merge ba lớp nói trên và trả về block kết quả ở dạng JSON. Script này dùng `tomllib` của Python stdlib, nên `python3` thuần là đủ:
```bash
npx bmad-method install
python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill {skill-root} \
--key agent
```
Trình cài đặt sẽ nhận diện bản cài đặt hiện có và đưa ra các lựa chọn sau:
**Yêu cầu**: Python 3.11+ vì các phiên bản cũ hơn không có `tomllib`. Không cần `pip install`, không cần `uv`, không cần virtualenv. Bạn có thể kiểm tra bằng `python3 --version`. Trên một số nền tảng, `python3` mặc định vẫn là 3.10 hoặc thấp hơn, nên có thể bạn sẽ phải cài 3.11+ riêng.
| Lựa chọn | Tác dụng |
| --- | --- |
| **Quick Update** | Cập nhật tất cả module lên phiên bản mới nhất và áp dụng các tùy chỉnh |
| **Modify BMad Installation** | Chạy lại quy trình cài đặt đầy đủ để thêm hoặc gỡ bỏ module |
`--skill` trỏ vào thư mục skill đã cài, nơi có file `customize.toml`. Tên skill được lấy từ basename của thư mục, sau đó script sẽ tự tìm `_bmad/custom/{skill-name}.toml``{skill-name}.user.toml`.
Nếu chỉ thay đổi phần tùy chỉnh, **Quick Update** là lựa chọn nhanh nhất.
Một số lệnh hữu ích:
## Khắc phục sự cố
```bash
# Resolve toàn bộ block agent
python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /duong-dan/tuyet-doi/toi/bmad-agent-pm \
--key agent
**Thay đổi không xuất hiện?**
# Resolve một trường cụ thể
python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /duong-dan/tuyet-doi/toi/bmad-agent-pm \
--key agent.icon
- Chạy `npx bmad-method install` và chọn **Quick Update** để áp dụng thay đổi
- Kiểm tra YAML có hợp lệ không (thụt lề rất quan trọng)
- Xác minh bạn đã sửa đúng tệp `.customize.yaml` của agent cần thiết
# Dump toàn bộ
python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /duong-dan/tuyet-doi/toi/bmad-agent-pm
```
**Agent không tải lên được?**
- Kiểm tra lỗi cú pháp YAML bằng một công cụ kiểm tra YAML trực tuyến
- Đảm bảo bạn không để trống trường nào sau khi bỏ comment
- Thử khôi phục mẫu gốc rồi build lại
**Cần đặt lại một agent?**
- Xóa nội dung hoặc xóa tệp `.customize.yaml` của agent đó
- Chạy `npx bmad-method install` và chọn **Quick Update** để khôi phục mặc định
Đầu ra luôn là JSON. Nếu script này không khả dụng trên một nền tảng nào đó, `SKILL.md` sẽ hướng dẫn agent đọc trực tiếp ba file TOML và áp dụng cùng các quy tắc merge.
## Tùy chỉnh workflow
Tài liệu về cách tùy chỉnh các workflow và skill sẵn có trong BMad Method sẽ được bổ sung trong thời gian tới.
Workflow, tức các skill điều phối tiến trình nhiều bước như `bmad-product-brief`, dùng cùng cơ chế override như agent. Khác biệt là bề mặt tùy chỉnh của chúng nằm dưới `[workflow]` thay vì `[agent]`:
## Tùy chỉnh module
```toml
# _bmad/custom/bmad-product-brief.toml
Hướng dẫn xây dựng expansion module và tùy chỉnh các module hiện có sẽ được bổ sung trong thời gian tới.
[workflow]
# Giống agent: prepend/append chạy trước và sau activation mặc định của
# workflow. Override sẽ append vào mặc định.
activation_steps_prepend = [
"Nạp {project-root}/docs/product/north-star-principles.md làm context.",
]
activation_steps_append = []
# Cũng dùng semantics literal-hoặc-file: như phía agent. Những fact này được
# nạp làm context nền tảng trong suốt lần chạy workflow.
persistent_facts = [
"Mọi brief đều phải có một mục explicit về regulatory risk.",
"file:{project-root}/docs/compliance/product-brief-checklist.md",
]
# Scalar: chạy đúng một lần khi workflow hoàn tất output chính. Override thắng.
on_complete = "Tóm tắt brief trong ba gạch đầu dòng rồi hỏi người dùng có muốn gửi email qua skill gws-gmail-send không."
```
Cùng một quy ước trường có thể đi xuyên qua ranh giới agent/workflow: `activation_steps_prepend`, `activation_steps_append`, `persistent_facts` với tham chiếu `file:`, và các table kiểu menu `[[...]]` dùng `code` hoặc `id` làm khóa merge. Resolver áp dụng đúng bốn quy tắc cấu trúc đã nêu bất kể top-level key là gì. Tham chiếu từ `SKILL.md` cũng theo namespace tương ứng: `{workflow.activation_steps_prepend}`, `{workflow.persistent_facts}`, `{workflow.on_complete}`. Mọi trường bổ sung mà một workflow tự expose, ví dụ output path, toggle, review setting hay stage flag, cũng sẽ đi theo cùng cơ chế merge dựa trên shape. Muốn biết chính xác workflow đó cho chỉnh gì, hãy đọc `customize.toml` của nó.
### Thứ tự activation
Workflow có thể tùy chỉnh sẽ chạy activation theo thứ tự cố định để bạn biết hook của mình được kích hoạt khi nào:
1. Resolve block `[workflow]` bằng merge base -> team -> user
2. Chạy `activation_steps_prepend` theo đúng thứ tự
3. Nạp `persistent_facts` làm ngữ cảnh nền tảng cho cả lần chạy
4. Nạp config (`_bmad/bmm/config.yaml`) và resolve các biến chuẩn như tên dự án, ngôn ngữ, đường dẫn, ngày tháng
5. Chào người dùng
6. Chạy `activation_steps_append` theo đúng thứ tự
Sau bước 6, phần thân chính của workflow mới bắt đầu. Hãy dùng `activation_steps_prepend` khi bạn cần load context trước cả lúc cá nhân hóa lời chào; dùng `activation_steps_append` khi phần thiết lập khá nặng và bạn muốn người dùng thấy lời chào trước.
### Phạm vi của đợt triển khai đầu tiên này
Khả năng tùy chỉnh đang được mở rộng dần. Những trường đã mô tả ở trên, gồm `activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`, là **bề mặt nền tảng** mà mọi workflow có thể tùy chỉnh đều sẽ hỗ trợ, và chúng sẽ ổn định qua các phiên bản. Ngày hôm nay, chỉ với những trường này bạn đã có thể kiểm soát những điểm lớn: thêm bước trước/sau, ghim context nền tảng, kích hoạt hành động tiếp theo sau khi workflow hoàn tất.
Theo thời gian, từng workflow sẽ expose thêm **các điểm tùy chỉnh chuyên biệt hơn** gắn với chính công việc của workflow đó, ví dụ toggle ở từng bước, stage flag, đường dẫn template đầu ra hoặc review gate. Khi những trường đó xuất hiện, chúng sẽ được chồng thêm lên bề mặt nền tảng chứ không thay thế nó, nên những tùy chỉnh bạn viết hôm nay vẫn tiếp tục dùng được.
Nếu bạn đang cần một "núm tinh chỉnh" chi tiết hơn nhưng workflow chưa expose, hãy tạm dùng `activation_steps_*``persistent_facts` để điều hướng hành vi, hoặc mở issue mô tả chính xác điểm tùy chỉnh bạn muốn. Chính những nhu cầu đó sẽ quyết định trường nào được bổ sung tiếp theo.
## Cấu hình trung tâm
`customize.toml` theo từng skill bao phủ **hành vi sâu** như hook, menu, `persistent_facts`, override persona cho một agent hay workflow đơn lẻ. Một bề mặt khác sẽ bao phủ **trạng thái cắt ngang** như các câu trả lời lúc cài đặt và roster agent mà những skill bên ngoài như `bmad-party-mode`, `bmad-retrospective``bmad-advanced-elicitation` sử dụng. Bề mặt đó nằm trong bốn file TOML ở root dự án:
```text
_bmad/config.toml (do installer quản lý) team scope: câu trả lời lúc cài đặt + agent roster
_bmad/config.user.toml (do installer quản lý) user scope: user_name, language, skill level
_bmad/custom/config.toml (do con người viết) team overrides (commit vào git)
_bmad/custom/config.user.toml (do con người viết) personal overrides (gitignore)
```
### Merge bốn lớp
```text
Ưu tiên 1 (thắng): _bmad/custom/config.user.toml
Ưu tiên 2: _bmad/custom/config.toml
Ưu tiên 3: _bmad/config.user.toml
Ưu tiên 4 (gốc): _bmad/config.toml
```
Các quy tắc cấu trúc hoàn toàn giống phần per-skill customize: scalar override, table deep-merge, mảng dùng `code` hoặc `id` sẽ merge theo khóa, các mảng khác thì append.
### Cái gì nằm ở đâu
Installer sẽ phân chia câu trả lời theo `scope:` khai báo trên từng prompt trong `module.yaml`:
- Các section `[core]``[modules.<code>]`: chứa câu trả lời khi cài. `scope = team` sẽ được ghi vào `_bmad/config.toml`; `scope = user` sẽ nằm trong `_bmad/config.user.toml`
- Section `[agents.<code>]`: "bản chất" của agent gồm code, name, title, icon, description, team, được chưng cất từ khối `agents:` trong `module.yaml` của từng module. Phần này luôn ở scope team
### Quy tắc chỉnh sửa
- `_bmad/config.toml``_bmad/config.user.toml` sẽ **được tạo lại sau mỗi lần cài đặt** từ những câu trả lời mà installer thu thập. Hãy coi chúng là output chỉ đọc; mọi chỉnh sửa trực tiếp sẽ bị ghi đè ở lần cài tiếp theo. Nếu muốn thay đổi bền vững một giá trị cài đặt, hãy chạy lại installer hoặc chồng giá trị đó bằng `_bmad/custom/config.toml`
- `_bmad/custom/config.toml``_bmad/custom/config.user.toml` sẽ **không bao giờ** bị installer động vào. Đây mới là bề mặt đúng để thêm custom agent, override descriptor của agent, ép các thiết lập dùng chung cho team và ghim mọi giá trị bạn muốn giữ nguyên bất kể câu trả lời lúc cài là gì
### Ví dụ: đổi thương hiệu cho một agent
```toml
# _bmad/custom/config.toml (commit vào git, áp dụng cho mọi developer)
[agents.bmad-agent-pm]
description = "PM trong domain healthcare, nhạy với compliance, luôn đặt câu hỏi theo hướng FDA ngay từ đầu."
icon = "🏥"
```
Resolver sẽ merge đè lên `[agents.bmad-agent-pm]` do installer sinh ra. `bmad-party-mode` và mọi roster consumer khác sẽ tự động thấy description mới này.
### Ví dụ: thêm một agent hư cấu
```toml
# _bmad/custom/config.user.toml (cá nhân, gitignore)
[agents.kirk]
team = "startrek"
name = "Captain James T. Kirk"
title = "Starship Captain"
icon = "🖖"
description = "Một chỉ huy táo bạo, thích bẻ luật. Nói chuyện có các quãng ngắt đầy kịch tính. Suy nghĩ thành tiếng về gánh nặng của quyền chỉ huy."
```
Không cần tạo thư mục skill. Chỉ riêng "essence" này cũng đủ để party-mode spawn Kirk như một giọng nói trong cuộc bàn tròn. Bạn có thể lọc theo trường `team` để chỉ mời nhóm Enterprise.
### Ví dụ: override thiết lập cài đặt của module
```toml
# _bmad/custom/config.toml
[modules.bmm]
planning_artifacts = "/shared/org-planning-artifacts"
```
Giá trị override này sẽ thắng mọi câu trả lời mà từng developer đã nhập khi cài trên máy của họ. Rất hữu ích khi bạn muốn ghim convention của cả team.
### Khi nào dùng bề mặt nào
| Nhu cầu | Bề mặt nên dùng |
|---|---|
| Thêm lời nhắc gọi MCP tool vào mọi dev workflow | Theo từng skill: `_bmad/custom/bmad-agent-dev.toml` trong `persistent_facts` |
| Thêm menu item cho một agent | Theo từng skill: `_bmad/custom/bmad-agent-{role}.toml` với `[[agent.menu]]` |
| Đổi template đầu ra của một workflow | Theo từng skill: `_bmad/custom/{workflow}.toml` bằng scalar override |
| Đổi descriptor công khai của một agent | **Cấu hình trung tâm**: `_bmad/custom/config.toml``[agents.<code>]` |
| Thêm custom agent hoặc agent hư cấu vào roster | **Cấu hình trung tâm**: `_bmad/custom/config*.toml` với entry mới `[agents.<code>]` |
| Ghim thiết lập cài đặt dùng chung của team | **Cấu hình trung tâm**: `_bmad/custom/config.toml` trong `[modules.<code>]` hoặc `[core]` |
Trong cùng một dự án, bạn hoàn toàn có thể dùng đồng thời cả hai bề mặt này.
## Ví dụ thực chiến
Để xem các recipe thiên về doanh nghiệp như định hình một agent trên mọi workflow mà nó dispatch, ép workflow tuân thủ convention nội bộ, publish output lên Confluence và Jira, tùy chỉnh agent roster, hoặc thay template đầu ra bằng template riêng của tổ chức, hãy xem [Cách mở rộng BMad cho tổ chức của bạn](./expand-bmad-for-your-org.md).
## Khắc phục sự cố
**Tùy chỉnh không xuất hiện?**
- Kiểm tra file của bạn có nằm đúng trong `_bmad/custom/` và dùng đúng tên skill không
- Kiểm tra cú pháp TOML: string phải có ngoặc kép, table header dùng `[section]`, array-of-tables dùng `[[section]]`, và mọi khóa scalar hay array của một table phải xuất hiện *trước* bất kỳ `[[subtables]]` nào của table đó trong file
- Với agent, phần tùy chỉnh phải nằm dưới `[agent]`, và các trường bên dưới header đó sẽ thuộc `agent` cho tới khi bạn mở table header khác
- Hãy nhớ rằng `agent.name``agent.title` là chỉ đọc, override vào đó sẽ không có tác dụng
**Tùy chỉnh bị hỏng sau khi update?**
- Bạn có copy nguyên file `customize.toml` vào file override không? **Đừng làm vậy.** File override chỉ nên chứa phần chênh lệch. Nếu copy nguyên file, bạn sẽ khóa cứng mặc định cũ và dần lệch khỏi các bản phát hành mới.
**Muốn biết có thể tùy chỉnh gì?**
- Chạy skill `bmad-customize`. Nó sẽ liệt kê mọi skill có thể tùy chỉnh trong dự án, cho biết skill nào đã có override, rồi dẫn bạn qua quá trình thêm hoặc sửa một override
- Hoặc đọc trực tiếp `customize.toml` của skill. Mọi trường ở đó đều có thể tùy chỉnh, trừ `name``title`
**Muốn reset?**
- Xóa file override của bạn trong `_bmad/custom/`, skill sẽ tự động rơi về cấu hình mặc định tích hợp sẵn

View File

@ -0,0 +1,266 @@
---
title: 'Cách mở rộng BMad cho tổ chức của bạn'
description: Năm mẫu tùy chỉnh giúp thay đổi BMad mà không cần fork, gồm quy tắc ở cấp agent, quy ước workflow, xuất bản ra hệ thống ngoài, thay template và điều chỉnh danh sách agent
sidebar:
order: 9
---
Bề mặt tùy chỉnh của BMad cho phép một tổ chức định hình lại hành vi mà không phải sửa file đã cài hay fork skill. Hướng dẫn này trình bày năm công thức mẫu (recipe) bao phủ phần lớn nhu cầu ở môi trường doanh nghiệp.
:::note[Điều kiện tiên quyết]
- BMad đã được cài trong dự án của bạn (xem [Cách cài đặt BMad](./install-bmad.md))
- Đã quen với mô hình tùy chỉnh (xem [Cách tùy chỉnh BMad](./customize-bmad.md))
- Python 3.11+ có trên PATH để chạy resolver, chỉ dùng stdlib, không cần `pip install`
:::
:::tip[Cách áp dụng các công thức mẫu này]
Những **công thức mẫu theo từng skill** bên dưới, tức Recipe 1 đến Recipe 4, có thể được áp dụng bằng cách chạy skill `bmad-customize` rồi mô tả ý định. Skill này sẽ tự chọn đúng bề mặt, viết file override và xác minh kết quả merge. Riêng Recipe 5, tức override cấu hình trung tâm để chỉnh danh sách agent (agent roster), hiện chưa nằm trong phạm vi v1 của skill nên vẫn cần viết tay. Các recipe trong trang này là nguồn sự thật cho phần *nên override cái gì*; `bmad-customize` phụ trách phần *thực hiện ra sao* ở lớp agent/workflow.
:::
## Mô hình ba lớp để suy nghĩ
Trước khi chọn recipe, bạn cần biết override của mình sẽ rơi vào đâu:
| Lớp | Nơi override sống | Phạm vi |
|---|---|---|
| **Agent** như Amelia, Mary, John | section `[agent]` trong `_bmad/custom/bmad-agent-{role}.toml` | Đi cùng persona vào **mọi workflow mà agent đó dispatch** |
| **Workflow** như `product-brief`, `create-prd` | section `[workflow]` trong `_bmad/custom/{workflow-name}.toml` | Chỉ áp dụng cho lần chạy của workflow đó |
| **Cấu hình trung tâm** | `[agents.*]`, `[core]`, `[modules.*]` trong `_bmad/custom/config.toml` | Agent roster và các thiết lập lúc cài đặt cần ghim cho cả tổ chức |
Nguyên tắc ngón tay cái:
- Nếu quy tắc nên áp dụng ở mọi nơi một engineer làm dev work, hãy tùy chỉnh **dev agent**
- Nếu nó chỉ áp dụng khi ai đó viết product brief, hãy tùy chỉnh **workflow product-brief**
- Nếu nó thay đổi *ai đang ngồi trong phòng* như đổi thương hiệu agent, thêm custom voice hoặc ép chung một artifact path, hãy sửa **cấu hình trung tâm**
## Recipe 1: định hình một agent trên mọi workflow mà nó điều phối (dispatch)
**Trường hợp dùng (use case):** Chuẩn hóa việc dùng công cụ và tích hợp với hệ thống bên ngoài để mọi workflow được dispatch qua agent đó tự động thừa hưởng cùng hành vi. Đây là mẫu áp dụng (pattern) có sức ảnh hưởng lớn nhất.
**Ví dụ:** Amelia, tức dev agent, luôn dùng Context7 cho tài liệu thư viện và fallback sang Linear nếu không tìm thấy story trong danh sách epic.
```toml
# _bmad/custom/bmad-agent-dev.toml
[agent]
# Áp dụng ở mọi lần kích hoạt. Theo Amelia đi vào dev-story, quick-dev,
# create-story, code-review, qa-generate và mọi skill cô ấy dispatch.
persistent_facts = [
"Với mọi truy vấn tài liệu thư viện như React, TypeScript, Zod, Prisma..., hãy gọi Context7 MCP tool (`mcp__context7__resolve_library_id` rồi `mcp__context7__get_library_docs`) trước khi dựa vào kiến thức trong dữ liệu huấn luyện (training data). Tài liệu cập nhật phải thắng API đã ghi nhớ.",
"Khi không tìm thấy tham chiếu story trong {planning_artifacts}/epics-and-stories.md, hãy tìm trong Linear bằng `mcp__linear__search_issues` theo ID hoặc tiêu đề story trước khi yêu cầu người dùng làm rõ. Nếu Linear trả về kết quả khớp, coi đó là nguồn story có thẩm quyền.",
]
```
**Vì sao cách này hiệu quả:** Chỉ với hai câu, bạn đã thay đổi mọi dev workflow trong tổ chức mà không lặp config từng nơi và không sửa source. Mọi engineer mới kéo repo về đều tự động thừa hưởng convention đó.
**File của team và file cá nhân**
- `bmad-agent-dev.toml`: commit vào git, áp dụng cho cả team
- `bmad-agent-dev.user.toml`: bị gitignore, dùng cho sở thích cá nhân chồng thêm lên trên
## Recipe 2: ép convention của tổ chức bên trong một workflow cụ thể
**Trường hợp dùng (use case):** Định hình *nội dung đầu ra* của một workflow để nó đáp ứng yêu cầu compliance, audit hoặc hệ thống downstream.
**Ví dụ:** mọi product brief đều phải có các trường compliance, và agent biết convention xuất bản của tổ chức.
```toml
# _bmad/custom/bmad-product-brief.toml
[workflow]
persistent_facts = [
"Mọi brief phải có trường 'Owner', 'Target Release' và 'Security Review Status'.",
"Các brief không mang tính thương mại như công cụ nội bộ hoặc dự án nghiên cứu vẫn phải có phần user value, nhưng có thể bỏ phân biệt cạnh tranh thị trường.",
"file:{project-root}/docs/enterprise/brief-publishing-conventions.md",
]
```
**Điều gì xảy ra:** Những fact này được nạp trong quá trình activation của workflow. Khi agent soạn brief, nó đã biết các trường bắt buộc và tài liệu convention nội bộ. Mặc định có sẵn, ví dụ `file:{project-root}/**/project-context.md`, vẫn tiếp tục được nạp vì phần này chỉ append thêm.
## Recipe 3: xuất bản kết quả hoàn tất sang hệ thống ngoài
**Trường hợp dùng (use case):** Sau khi workflow tạo ra output chính, tự động đẩy nó sang hệ thống nguồn sự thật của doanh nghiệp như Confluence, Notion, SharePoint, rồi mở tiếp công việc follow-up trong Jira, Linear hoặc Asana.
**Ví dụ:** brief được tự động publish lên Confluence và tùy chọn mở Jira epic.
```toml
# _bmad/custom/bmad-product-brief.toml
[workflow]
# Hook ở giai đoạn cuối. Scalar override sẽ thay hẳn mặc định rỗng.
on_complete = """
Publish và đề nghị bước tiếp theo:
1. Đọc đường dẫn file brief đã hoàn tất từ bước trước.
2. Gọi `mcp__atlassian__confluence_create_page` với:
- space: "PRODUCT"
- parent: "Product Briefs"
- title: tiêu đề của brief
- body: nội dung markdown của brief
Lưu lại URL trang được trả về.
3. Thông báo cho người dùng: "Brief đã được publish lên Confluence: <url>".
4. Hỏi: "Bạn có muốn tôi mở Jira epic cho brief này ngay bây giờ không?"
5. Nếu có, gọi `mcp__atlassian__jira_create_issue` với:
- type: "Epic"
- project: "PROD"
- summary: tiêu đề của brief
- description: tóm tắt ngắn cùng liên kết ngược về trang Confluence.
Sau đó báo lại epic key và URL.
6. Nếu không, thoát sạch.
Nếu một trong các MCP tool bị lỗi, hãy báo lỗi, in ra đường dẫn brief
và yêu cầu người dùng publish thủ công.
"""
```
**Vì sao dùng `on_complete` thay vì `activation_steps_append`:** `on_complete` chỉ chạy đúng một lần ở cuối, sau khi output chính của workflow đã được ghi ra. Đó là thời điểm đúng để publish artifact. `activation_steps_append` thì chạy mỗi lần kích hoạt, trước khi workflow làm công việc chính của nó.
**Điểm đánh đổi (trade-offs)**
- Publish lên Confluence là hành động không phá hủy, nên có thể luôn chạy khi hoàn tất
- Tạo Jira epic là hành động hiển thị cho cả team và kích hoạt các tín hiệu sprint planning, nên nên chặn bởi một bước xác nhận từ người dùng
- Nếu MCP tool lỗi, workflow phải có phương án dự phòng (fallback) rõ ràng thay vì âm thầm làm mất output
## Recipe 4: thay output template bằng template của riêng bạn
**Trường hợp dùng (use case):** Cấu trúc đầu ra mặc định không khớp định dạng mà tổ chức mong muốn, hoặc trong cùng một repo có nhiều tổ chức cần template riêng.
**Ví dụ:** trỏ workflow product-brief sang template do doanh nghiệp sở hữu.
```toml
# _bmad/custom/bmad-product-brief.toml
[workflow]
brief_template = "{project-root}/docs/enterprise/brief-template.md"
```
**Cách nó hoạt động:** `customize.toml` của workflow đi kèm `brief_template = "resources/brief-template.md"` dưới dạng đường dẫn tương đối tới skill root. Override của bạn lại trỏ tới một file trong `{project-root}`, nên agent sẽ đọc template của bạn trong bước tương ứng thay vì dùng template mặc định đi kèm.
**Mẹo viết template**
- Giữ template trong `{project-root}/docs/` hoặc `{project-root}/_bmad/custom/templates/` để nó được version cùng với file override
- Nên dùng cùng convention cấu trúc với template mặc định, ví dụ heading và frontmatter, để agent có điểm tựa ổn định
- Với repo đa tổ chức, hãy dùng `.user.toml` để từng nhóm nhỏ có thể trỏ sang template riêng mà không cần sửa file dùng chung của team
## Recipe 5: tùy chỉnh danh sách agent (agent roster)
**Trường hợp dùng (use case):** Thay đổi *ai đang ngồi trong phòng* cho những skill dựa trên roster như `bmad-party-mode`, `bmad-retrospective``bmad-advanced-elicitation`, mà không cần sửa source hay fork. Dưới đây là ba biến thể thường gặp.
### 5a. Rebrand một agent của BMad trên toàn tổ chức
Mỗi agent thật đều có một descriptor được installer tổng hợp từ `module.yaml`. Bạn có thể override descriptor này để đổi giọng điệu và framing ở mọi roster consumer:
```toml
# _bmad/custom/config.toml (commit vào git, áp dụng cho mọi developer)
[agents.bmad-agent-analyst]
description = "Mary, nhà phân tích nghiệp vụ giàu nhận thức pháp lý, pha trộn Porter với Minto nhưng sống cùng các audit trail của FDA. Cô ấy nói như một điều tra viên pháp chứng đang trình bày hồ sơ vụ án."
```
Party mode sẽ spawn Mary với description mới này. Bản thân activation của analyst vẫn chạy bình thường vì hành vi của Mary sống trong `customize.toml` theo từng skill. Override này chỉ thay đổi cách **các skill bên ngoài nhìn thấy và giới thiệu cô ấy**, chứ không thay đổi cách cô ấy hoạt động bên trong.
### 5b. Thêm một agent hư cấu hoặc agent tự định nghĩa
Chỉ cần một descriptor đầy đủ là đủ cho các tính năng dựa trên roster, không cần thư mục skill. Điều này rất phù hợp nếu bạn muốn tăng màu sắc tính cách cho party mode hay các buổi brainstorming:
```toml
# _bmad/custom/config.user.toml (cá nhân, gitignore)
[agents.spock]
team = "startrek"
name = "Commander Spock"
title = "Science Officer"
icon = "🖖"
description = "Logic là trên hết, cảm xúc bị nén lại. Mở đầu nhận xét bằng 'Fascinating.' Không bao giờ làm tròn lên. Là đối trọng với mọi lập luận chỉ dựa vào linh cảm."
[agents.mccoy]
team = "startrek"
name = "Dr. Leonard McCoy"
title = "Chief Medical Officer"
icon = "⚕️"
description = "Sự ấm áp của một bác sĩ miền quê, đi kèm với tính nóng nảy. 'Dammit Jim, I'm a doctor not a ___.' Là đối trọng đạo đức với Spock."
```
Khi bạn yêu cầu party-mode "mời nhóm Star Trek" hoặc "mời phi hành đoàn Enterprise", nó sẽ lọc theo `team = "startrek"` và spawn Spock cùng McCoy dựa trên các descriptor đó. Các agent thật của BMad như Mary hay Amelia vẫn có thể ngồi cùng bàn nếu bạn muốn.
### 5c. Ghim thiết lập cài đặt dùng chung cho cả team
Installer sẽ hỏi từng developer các giá trị như đường dẫn `planning_artifacts`. Khi tổ chức muốn có một câu trả lời thống nhất, hãy ghim nó trong cấu hình trung tâm. Khi đó, mọi câu trả lời cục bộ của từng người sẽ bị override lúc resolve:
```toml
# _bmad/custom/config.toml
[modules.bmm]
planning_artifacts = "{project-root}/shared/planning"
implementation_artifacts = "{project-root}/shared/implementation"
[core]
document_output_language = "English"
```
Những thiết lập cá nhân như `user_name`, `communication_language` hoặc `user_skill_level` nên vẫn nằm trong `_bmad/config.user.toml` riêng của từng developer. File chung của team không nên đụng vào các giá trị đó.
**Vì sao việc này nằm ở cấu hình trung tâm thay vì per-agent customize.toml:** File per-agent chỉ định hình cách *một* agent hành xử khi nó được kích hoạt. Cấu hình trung tâm lại định hình những gì các roster consumer *nhìn thấy khi quan sát cánh đồng chung*: agent nào tồn tại, tên gì, thuộc team nào và các thiết lập cài đặt dùng chung mà toàn repo đã thống nhất. Hai bề mặt khác nhau, hai công việc khác nhau.
## Củng cố các quy tắc toàn cục trong file hướng dẫn phiên của IDE
Tùy chỉnh của BMad chỉ được nạp khi một skill được kích hoạt. Trong khi đó, nhiều công cụ IDE còn nạp một file hướng dẫn toàn cục ở **đầu mọi phiên**, trước cả khi skill nào chạy, như `CLAUDE.md`, `AGENTS.md`, `.cursor/rules/` hay `.github/copilot-instructions.md`. Với những quy tắc phải đúng cả khi bạn đang chat thường, hãy lặp lại phiên bản rút gọn của chúng trong file đó nữa.
**Khi nào nên "đánh đôi"**
- Quy tắc đó đủ quan trọng đến mức một cuộc chat thường, chưa kích hoạt BMad skill nào, cũng vẫn phải tuân theo
- Bạn muốn áp dụng kiểu "gia cố hai lớp" (belt-and-suspenders) vì hành vi mặc định từ dữ liệu huấn luyện (training data) có thể kéo model đi chệch
- Quy tắc đủ ngắn để lặp lại mà không làm file hướng dẫn đầu phiên trở nên phình to
**Ví dụ:** một dòng trong `CLAUDE.md` của repo để củng cố quy tắc ở Recipe 1.
```markdown
<!-- Mọi lần đọc tài liệu thư viện phải đi qua Context7 MCP tool
(`mcp__context7__resolve_library_id` rồi `mcp__context7__get_library_docs`)
trước khi dựa vào kiến thức từ dữ liệu huấn luyện (training data). -->
```
Chỉ một câu, nhưng được nạp ở mọi phiên. Nó kết hợp với cấu hình `bmad-agent-dev.toml` để quy tắc có hiệu lực cả trong workflow của Amelia lẫn trong các cuộc trò chuyện ad-hoc với assistant. Mỗi lớp giữ đúng phạm vi của mình:
| Lớp | Phạm vi | Dùng cho |
|---|---|---|
| File hướng dẫn phiên của IDE như `CLAUDE.md` hoặc `AGENTS.md` | Mọi phiên, trước khi bất kỳ skill nào chạy | Quy tắc ngắn, phổ quát, phải sống cả ngoài BMad |
| Tùy chỉnh agent của BMad | Mọi workflow mà agent đó dispatch | Hành vi riêng theo persona/agent |
| Tùy chỉnh workflow của BMad | Một lần chạy workflow | Dạng đầu ra, hook publish, template và logic riêng của workflow |
| Cấu hình trung tâm của BMad | Agent roster và thiết lập cài đặt dùng chung | Ai đang ngồi trong phòng và đường dẫn nào cả team dùng chung |
Hãy giữ file hướng dẫn của IDE **ngắn gọn**. Một tá dòng được chọn kỹ sẽ hiệu quả hơn một danh sách dài lê thê. Model phải đọc file đó ở mọi lượt, và càng nhiều nhiễu thì càng ít tín hiệu.
## Kết hợp các recipe
Cả năm recipe này có thể kết hợp song song. Một cấu hình doanh nghiệp thực tế cho `bmad-product-brief` hoàn toàn có thể đặt `persistent_facts` theo Recipe 2, `on_complete` theo Recipe 3 và `brief_template` theo Recipe 4 trong cùng một file. Quy tắc ở cấp agent theo Recipe 1 sẽ nằm trong file của agent tương ứng, còn cấu hình trung tâm theo Recipe 5 thì ghim roster và thiết lập chung. Tất cả cùng hoạt động đồng thời.
```toml
# _bmad/custom/bmad-product-brief.toml (cấp workflow)
[workflow]
persistent_facts = ["..."]
brief_template = "{project-root}/docs/enterprise/brief-template.md"
on_complete = """ ... """
```
```toml
# _bmad/custom/bmad-agent-analyst.toml (cấp agent, Mary sẽ dispatch product-brief)
[agent]
persistent_facts = ["Luôn thêm mục 'Regulatory Review' khi domain liên quan tới healthcare, finance hoặc dữ liệu trẻ em."]
```
Kết quả là Mary nạp quy tắc review pháp lý ngay ở lúc kích hoạt persona. Khi người dùng chọn menu item product-brief, workflow sẽ nạp các convention riêng của nó chồng lên, ghi ra template của doanh nghiệp và publish lên Confluence khi hoàn tất. Mỗi lớp đều đóng góp một phần và không lớp nào đòi hỏi sửa source của BMad.
## Khắc phục sự cố
**Override không có tác dụng?** Hãy kiểm tra file có nằm trong `_bmad/custom/` và dùng đúng tên thư mục skill không, ví dụ `bmad-agent-dev.toml`, chứ không phải `bmad-dev.toml`. Nếu cần, xem lại [Cách tùy chỉnh BMad](./customize-bmad.md).
**Không chắc tên MCP tool?** Hãy dùng đúng tên mà MCP server hiện tại expose trong phiên của bạn. Nếu chưa chắc, hãy yêu cầu Claude Code liệt kê các MCP tool đang có. Những tên hardcode trong `persistent_facts` hay `on_complete` sẽ không chạy nếu MCP server chưa được kết nối.
**Mẫu áp dụng (pattern) trong ví dụ không khớp setup của tôi?** Các recipe trên chỉ là ví dụ mẫu. Cơ chế bên dưới, gồm merge ba lớp, quy tắc cấu trúc và mô hình agent-span-workflow, vẫn hỗ trợ nhiều pattern khác. Hãy kết hợp chúng theo nhu cầu thực tế của bạn.

View File

@ -0,0 +1,180 @@
---
title: 'Cài đặt module tùy chỉnh và module cộng đồng'
description: Cài các module bên thứ ba từ kho cộng đồng (community registry), kho Git hoặc đường dẫn cục bộ
sidebar:
order: 3
---
Sử dụng trình cài đặt BMad để thêm module từ kho cộng đồng (community registry), kho Git của bên thứ ba hoặc đường dẫn file cục bộ.
## Khi nào nên dùng
- Cài một module do cộng đồng đóng góp từ BMad registry
- Cài module từ kho Git của bên thứ ba như GitHub, GitLab, Bitbucket hoặc máy chủ tự host
- Kiểm thử một module bạn đang phát triển cục bộ với BMad Builder
- Cài module từ máy chủ Git riêng tư hoặc tự host
:::note[Điều kiện tiên quyết]
Yêu cầu [Node.js](https://nodejs.org) v20+ và `npx` đi kèm npm. Bạn có thể chọn module tùy chỉnh và module cộng đồng trong lúc cài mới, hoặc thêm chúng vào một bản cài hiện có.
:::
## Module cộng đồng
Các module cộng đồng được tuyển chọn trong [BMad plugins marketplace](https://github.com/bmad-code-org/bmad-plugins-marketplace). Chúng được sắp theo danh mục và được ghim vào commit đã được phê duyệt để tăng độ an toàn.
### 1. Chạy trình cài đặt
```bash
npx bmad-method install
```
### 2. Duyệt danh mục (catalog) cộng đồng
Sau khi chọn module chính thức, trình cài đặt sẽ hỏi:
```
Would you like to browse community modules?
```
Chọn **Yes** để vào màn hình duyệt catalog. Tại đây bạn có thể:
- Duyệt theo danh mục
- Xem các module nổi bật
- Xem toàn bộ module khả dụng
- Tìm kiếm theo từ khóa
### 3. Chọn module
Chọn module từ bất kỳ danh mục nào. Trình cài đặt sẽ hiển thị mô tả, phiên bản và mức độ tin cậy (trust tier). Những module đã cài sẽ được tick sẵn để tiện cập nhật.
### 4. Tiếp tục quá trình cài đặt
Sau khi chọn xong module cộng đồng, trình cài đặt sẽ chuyển sang bước nguồn tùy chỉnh (custom source), rồi tới cấu hình tool/IDE và phần còn lại của luồng cài đặt.
## Nguồn tùy chỉnh: Git URL và đường dẫn cục bộ
Module tùy chỉnh có thể đến từ bất kỳ kho Git nào hoặc từ một thư mục cục bộ trên máy bạn. Trình cài đặt sẽ resolve nguồn, phân tích cấu trúc module rồi cài nó song song với các module khác.
### Cài đặt tương tác
Trong quá trình cài, sau bước chọn community module, trình cài đặt sẽ hỏi:
```
Would you like to install from a custom source (Git URL or local path)?
```
Chọn **Yes**, rồi nhập nguồn:
| Loại đầu vào | Ví dụ |
| --------------------- | ------------------------------------------------- |
| HTTPS URL trên bất kỳ host nào | `https://github.com/org/repo` |
| HTTPS URL trỏ vào một thư mục con | `https://github.com/org/repo/tree/main/my-module` |
| SSH URL | `git@github.com:org/repo.git` |
| Đường dẫn cục bộ | `/Users/me/projects/my-module` |
| Đường dẫn cục bộ dùng `~` | `~/projects/my-module` |
Với URL, trình cài đặt sẽ clone repository. Với đường dẫn cục bộ, nó sẽ đọc trực tiếp từ đĩa. Sau đó nó sẽ hiển thị các module tìm thấy để bạn chọn cài.
### Cài đặt không tương tác
Dùng cờ `--custom-source` để cài module tùy chỉnh từ dòng lệnh:
```bash
npx bmad-method install \
--directory . \
--custom-source /path/to/my-module \
--tools claude-code \
--yes
```
Khi cung cấp `--custom-source` mà không kèm `--modules`, hệ thống chỉ cài core và các module tùy chỉnh. Nếu muốn cài cả module chính thức, hãy thêm `--modules`:
```bash
npx bmad-method install \
--directory . \
--modules bmm \
--custom-source https://gitlab.com/myorg/my-module \
--tools claude-code \
--yes
```
Bạn có thể truyền nhiều nguồn bằng cách ngăn cách chúng bằng dấu phẩy:
```bash
--custom-source /path/one,https://github.com/org/repo,/path/two
```
## Cơ chế phát hiện module
Trình cài đặt dùng hai chế độ để tìm module có thể cài trong một nguồn:
| Chế độ | Điều kiện kích hoạt | Hành vi |
| --------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Discovery | Nguồn chứa `.claude-plugin/marketplace.json` | Liệt kê toàn bộ plugin trong manifest để bạn chọn cái nào cần cài |
| Direct | Không tìm thấy `marketplace.json` | Quét thư mục để tìm các skill, tức các thư mục con chứa `SKILL.md`, rồi coi toàn bộ như một module duy nhất |
Discovery là chế độ phát hiện qua manifest. Direct là chế độ quét trực tiếp thư mục. Discovery phù hợp với module đã publish, còn Direct thuận tiện khi bạn đang trỏ vào một thư mục skills trong quá trình phát triển cục bộ.
:::note[Về thư mục `.claude-plugin/`]
Đường dẫn `.claude-plugin/marketplace.json` là một quy ước tiêu chuẩn được nhiều trình cài đặt AI tool cùng dùng để hỗ trợ khả năng khám phá plugin. Nó không đòi hỏi Claude, không dùng Claude API và cũng không ảnh hưởng tới việc bạn đang dùng công cụ AI nào. Bất kỳ module nào có file này đều có thể được khám phá bởi những trình cài đặt tuân theo cùng quy ước.
:::
## Quy trình phát triển cục bộ
Nếu bạn đang xây một module bằng [BMad Builder](https://github.com/bmad-code-org/bmad-builder), bạn có thể cài trực tiếp từ thư mục đang làm việc:
```bash
npx bmad-method install \
--directory ~/my-project \
--custom-source ~/my-module-repo/skills \
--tools claude-code \
--yes
```
Nguồn cục bộ được tham chiếu theo đường dẫn, không bị copy vào cache. Khi bạn sửa source của module rồi cài lại, trình cài đặt sẽ lấy đúng các thay đổi mới nhất.
:::caution[Xóa nguồn sau khi cài]
Nếu bạn xóa thư mục nguồn cục bộ sau khi cài, các file module đã được cài bên trong `_bmad/` vẫn được giữ nguyên. Tuy vậy, module đó sẽ bị bỏ qua trong các lần cập nhật cho tới khi đường dẫn nguồn được khôi phục.
:::
## Bạn sẽ nhận được gì
Sau khi cài, các module tùy chỉnh sẽ xuất hiện trong `_bmad/` cùng với module chính thức:
```text
your-project/
├── _bmad/
│ ├── core/ # Module core tích hợp
│ ├── bmm/ # Module chính thức, nếu bạn chọn
│ ├── my-module/ # Module tùy chỉnh của bạn
│ │ ├── my-skill/
│ │ │ └── SKILL.md
│ │ └── module-help.csv
│ └── _config/
│ └── manifest.yaml # Theo dõi mọi module, phiên bản và nguồn
└── ...
```
Manifest sẽ ghi lại nguồn của từng module tùy chỉnh, dùng `repoUrl` cho nguồn Git và `localPath` cho nguồn cục bộ, để quá trình cập nhật nhanh (quick update) sau này có thể tìm lại nguồn chính xác.
## Cập nhật module tùy chỉnh
Module tùy chỉnh tham gia vào luồng cập nhật bình thường:
- **Cập nhật nhanh (quick update)** với `--action quick-update`: làm mới mọi module từ đúng nguồn ban đầu. Module dựa trên Git sẽ được fetch lại, còn module cục bộ sẽ được đọc lại từ đường dẫn nguồn
- **Cập nhật đầy đủ (full update)**: chạy lại bước chọn module để bạn có thể thêm hoặc gỡ module tùy chỉnh
## Tạo module của riêng bạn
Hãy dùng [BMad Builder](https://github.com/bmad-code-org/bmad-builder) để tạo module mà người khác có thể cài:
1. Chạy `bmad-module-builder` để sinh skeleton cho module
2. Thêm skill, agent và workflow bằng các công cụ builder tương ứng
3. Publish lên một kho Git hoặc chia sẻ cả thư mục
4. Người khác có thể cài bằng `--custom-source <url-kho-cua-ban>`
Nếu muốn module hỗ trợ chế độ Discovery, hãy thêm `.claude-plugin/marketplace.json` ở root repository. Đây là quy ước chung giữa nhiều công cụ, không dành riêng cho Claude. Hãy xem [tài liệu của BMad Builder](https://github.com/bmad-code-org/bmad-builder) để biết định dạng của `marketplace.json`.
:::tip[Hãy thử cục bộ trước]
Trong quá trình phát triển, hãy cài module bằng đường dẫn cục bộ để lặp nhanh trước khi publish lên kho Git.
:::

View File

@ -28,6 +28,7 @@ Yêu cầu [Node.js](https://nodejs.org) v20+ và `npx` (đi kèm với npm).
| `--modules <modules>` | Danh sách ID module, cách nhau bởi dấu phẩy | `--modules bmm,bmb` |
| `--tools <tools>` | Danh sách ID công cụ/IDE, cách nhau bởi dấu phẩy (dùng `none` để bỏ qua) | `--tools claude-code,cursor` hoặc `--tools none` |
| `--action <type>` | Hành động cho bản cài đặt hiện có: `install` (mặc định), `update`, hoặc `quick-update` | `--action quick-update` |
| `--custom-source <sources>` | Danh sách Git URL hoặc đường dẫn cục bộ cho module tùy chỉnh, cách nhau bởi dấu phẩy | `--custom-source /path/to/module` |
### Cấu hình cốt lõi
@ -81,6 +82,7 @@ Chạy `npx bmad-method install` một lần ở chế độ tương tác để
| Hoàn toàn không tương tác | Cung cấp đầy đủ cờ để bỏ qua tất cả prompt | `npx bmad-method install --directory . --modules bmm --tools claude-code --yes` |
| Bán tương tác | Cung cấp một số cờ, BMad hỏi thêm phần còn lại | `npx bmad-method install --directory . --modules bmm` |
| Chỉ dùng mặc định | Chấp nhận tất cả giá trị mặc định với `-y` | `npx bmad-method install --yes` |
| Chỉ dùng custom source | Chỉ cài core và module tùy chỉnh | `npx bmad-method install --directory . --custom-source /path/to/module --tools claude-code --yes` |
| Không cấu hình công cụ | Bỏ qua cấu hình công cụ/IDE | `npx bmad-method install --modules bmm --tools none` |
## Ví dụ
@ -119,6 +121,33 @@ npx bmad-method install \
--action quick-update
```
### Cài từ custom source
Cài một module từ đường dẫn cục bộ hoặc từ bất kỳ Git host nào:
```bash
npx bmad-method install \
--directory . \
--custom-source /path/to/my-module \
--tools claude-code \
--yes
```
Kết hợp cùng module chính thức:
```bash
npx bmad-method install \
--directory . \
--modules bmm \
--custom-source https://gitlab.com/myorg/my-module \
--tools claude-code \
--yes
```
:::note[Hành vi của `custom-source`]
Khi dùng `--custom-source` mà không kèm `--modules`, hệ thống chỉ cài core và các module tùy chỉnh. Nếu muốn cài cả module chính thức, hãy thêm `--modules`. Xem thêm [Cài đặt module tùy chỉnh và module cộng đồng](./install-custom-modules.md) để biết chi tiết.
:::
## Bạn nhận được gì
- Thư mục `_bmad/` đã được cấu hình đầy đủ trong dự án của bạn

42
package-lock.json generated
View File

@ -15,7 +15,6 @@
"chalk": "^4.1.2",
"commander": "^14.0.0",
"csv-parse": "^6.1.0",
"fs-extra": "^11.3.0",
"glob": "^11.0.3",
"ignore": "^7.0.5",
"js-yaml": "^4.1.0",
@ -25,8 +24,8 @@
"yaml": "^2.7.0"
},
"bin": {
"bmad": "tools/bmad-npx-wrapper.js",
"bmad-method": "tools/bmad-npx-wrapper.js"
"bmad": "tools/installer/bmad-cli.js",
"bmad-method": "tools/installer/bmad-cli.js"
},
"devDependencies": {
"@astrojs/sitemap": "^3.6.0",
@ -46,6 +45,7 @@
"prettier": "^3.7.4",
"prettier-plugin-packagejson": "^2.5.19",
"sharp": "^0.33.5",
"unist-util-visit": "^5.1.0",
"yaml-eslint-parser": "^1.2.3",
"yaml-lint": "^1.7.0"
},
@ -6975,20 +6975,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fs-extra": {
"version": "11.3.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -7227,6 +7213,7 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/h3": {
@ -9066,18 +9053,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/katex": {
"version": "0.16.28",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz",
@ -13607,15 +13582,6 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/unrs-resolver": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",

View File

@ -41,7 +41,8 @@
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run validate:refs && npm run validate:skills",
"rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",
"test": "npm run test:refs && npm run test:install && npm run lint && npm run lint:md && npm run format:check",
"test": "npm run test:refs && npm run test:install && npm run test:channels && npm run lint && npm run lint:md && npm run format:check",
"test:channels": "node test/test-installer-channels.js",
"test:install": "node test/test-installation-components.js",
"test:refs": "node test/test-file-refs-csv.js",
"validate:refs": "node tools/validate-file-refs.js --strict",

View File

@ -3,4 +3,60 @@ name: bmad-document-project
description: 'Document brownfield projects for AI context. Use when the user says "document this project" or "generate project docs"'
---
Follow the instructions in ./workflow.md.
# Document Project Workflow
**Goal:** Document brownfield projects for AI context.
**Your Role:** Project documentation specialist.
## Conventions
- Bare paths (e.g. `instructions.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}` (if you have not already), speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Execution
Read fully and follow: `./instructions.md`

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-document-project. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All briefs must include a regulatory-risk section."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches its terminal stage, after
# the main output has been delivered. Override wins. Leave empty for
# no custom post-completion behavior.
on_complete = ""

View File

@ -1,25 +0,0 @@
# Document Project Workflow
**Goal:** Document brownfield projects for AI context.
**Your Role:** Project documentation specialist.
- Communicate all responses in {communication_language}
---
## INITIALIZATION
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
2. **Greet user** as `{user_name}`, speaking in `{communication_language}`.
---
## EXECUTION
Read fully and follow: `./instructions.md`

View File

@ -291,6 +291,7 @@ These comprehensive docs are now ready for:
Thank you for using the document-project workflow!
</action>
<action>Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
<action>Exit workflow</action>
</action>
</step>

View File

@ -1103,5 +1103,6 @@ When ready to plan new features, run the PRD workflow and provide this index as
</action>
<action>Display: "State file saved: {{project_knowledge}}/project-scan-report.json"</action>
<action>Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
</workflow>

View File

@ -19,20 +19,59 @@ The PRFAQ forces customer-first clarity: write the press release announcing the
**Research-grounded.** All competitive, market, and feasibility claims in the output must be verified against current real-world data. Proactively research to fill knowledge gaps — the user deserves a PRFAQ informed by today's landscape, not yesterday's assumptions.
## Conventions
- Bare paths (e.g. `references/press-release.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## On Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 1: Resolve the Workflow Block
2. **Greet user** as `{user_name}`, speaking in `{communication_language}`. Be warm but efficient — dream builder energy.
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
3. **Resume detection:** Check if `{planning_artifacts}/prfaq-{project_name}.md` already exists. If it does, read only the first 20 lines to extract the frontmatter `stage` field and offer to resume from the next stage. Do not read the full document. If the user confirms, route directly to that stage's reference file.
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
4. **Mode detection:**
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`. Be warm but efficient — dream builder energy.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Continue below.
## Pre-workflow Setup
1. **Resume detection:** Check if `{planning_artifacts}/prfaq-{project_name}.md` already exists. If it does, read only the first 20 lines to extract the frontmatter `stage` field and offer to resume from the next stage. Do not read the full document. If the user confirms, route directly to that stage's reference file.
2. **Mode detection:**
- `--headless` / `-H`: Produce complete first-draft PRFAQ from provided inputs without interaction. Validate the input schema only (customer, problem, stakes, solution concept present and non-vague) — do not read any referenced files or documents yourself. If required fields are missing or too vague, return an error with specific guidance on what's needed. Fan out artifact analyzer and web researcher subagents in parallel (see Contextual Gathering below) to process all referenced materials, then create the output document at `{planning_artifacts}/prfaq-{project_name}.md` using `./assets/prfaq-template.md` and route to `./references/press-release.md`.
- Default: Full interactive coaching — the gauntlet.

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-prfaq. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All briefs must include a regulatory-risk section."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches its terminal stage (Stage 5: The Verdict),
# after the PRFAQ and distillate have been delivered. Override wins. Leave empty for
# no custom post-completion behavior.
on_complete = ""

View File

@ -77,3 +77,7 @@ purpose: "Token-efficient context for downstream PRD creation"
## Stage Complete
This is the terminal stage. If the user wants to revise, loop back to the relevant stage. Otherwise, the workflow is done.
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.

View File

@ -3,4 +3,94 @@ name: bmad-domain-research
description: 'Conduct domain and industry research. Use when the user says wants to do domain research for a topic or industry'
---
Follow the instructions in ./workflow.md.
# Domain Research Workflow
**Goal:** Conduct comprehensive domain/industry research using current web data and verified sources to produce complete research documents with compelling narratives and proper citations.
**Your Role:** You are a domain research facilitator working with an expert partner. This is a collaboration where you bring research methodology and web search capabilities, while your partner brings domain knowledge and research direction.
## Conventions
- Bare paths (e.g. `domain-steps/step-01-init.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## PREREQUISITE
**⛔ Web search required.** If unavailable, abort and tell the user.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## QUICK TOPIC DISCOVERY
"Welcome {{user_name}}! Let's get started with your **domain/industry research**.
**What domain, industry, or sector do you want to research?**
For example:
- 'The healthcare technology industry'
- 'Sustainable packaging regulations in Europe'
- 'Construction and building materials sector'
- 'Or any other domain you have in mind...'"
### Topic Clarification
Based on the user's topic, briefly clarify:
1. **Core Domain**: "What specific aspect of [domain] are you most interested in?"
2. **Research Goals**: "What do you hope to achieve with this research?"
3. **Scope**: "Should we focus broadly or dive deep into specific aspects?"
## ROUTE TO DOMAIN RESEARCH STEPS
After gathering the topic and goals:
1. Set `research_type = "domain"`
2. Set `research_topic = [discovered topic from discussion]`
3. Set `research_goals = [discovered goals from discussion]`
4. Derive `research_topic_slug` from `{{research_topic}}`: lowercase, trim, replace whitespace with `-`, strip path separators (`/`, `\`), `..`, and any character that is not alphanumeric, `-`, or `_`. Collapse repeated `-` and strip leading/trailing `-`. If the result is empty, use `untitled`.
5. Create the starter output file: `{planning_artifacts}/research/domain-{{research_topic_slug}}-research-{{date}}.md` with exact copy of the `./research.template.md` contents
6. Load: `./domain-steps/step-01-init.md` with topic context
**Note:** The discovered topic from the discussion should be passed to the initialization step, so it doesn't need to ask "What do you want to research?" again - it can focus on refining the scope for domain research.
**✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`**

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-domain-research. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All briefs must include a regulatory-risk section."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches its terminal stage (Step 6: Research Synthesis),
# after the domain research document has been saved and the user selects [C] Complete.
# Override wins. Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -441,4 +441,10 @@ Complete authoritative research document on {{research_topic}} that:
- Serves as reference document for continued use
- Maintains highest research quality standards
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
Congratulations on completing comprehensive domain research! 🎉

View File

@ -1,51 +0,0 @@
# Domain Research Workflow
**Goal:** Conduct comprehensive domain/industry research using current web data and verified sources to produce complete research documents with compelling narratives and proper citations.
**Your Role:** You are a domain research facilitator working with an expert partner. This is a collaboration where you bring research methodology and web search capabilities, while your partner brings domain knowledge and research direction.
## PREREQUISITE
**⛔ Web search required.** If unavailable, abort and tell the user.
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
## QUICK TOPIC DISCOVERY
"Welcome {{user_name}}! Let's get started with your **domain/industry research**.
**What domain, industry, or sector do you want to research?**
For example:
- 'The healthcare technology industry'
- 'Sustainable packaging regulations in Europe'
- 'Construction and building materials sector'
- 'Or any other domain you have in mind...'"
### Topic Clarification
Based on the user's topic, briefly clarify:
1. **Core Domain**: "What specific aspect of [domain] are you most interested in?"
2. **Research Goals**: "What do you hope to achieve with this research?"
3. **Scope**: "Should we focus broadly or dive deep into specific aspects?"
## ROUTE TO DOMAIN RESEARCH STEPS
After gathering the topic and goals:
1. Set `research_type = "domain"`
2. Set `research_topic = [discovered topic from discussion]`
3. Set `research_goals = [discovered goals from discussion]`
4. Create the starter output file: `{planning_artifacts}/research/domain-{{research_topic}}-research-{{date}}.md` with exact copy of the `./research.template.md` contents
5. Load: `./domain-steps/step-01-init.md` with topic context
**Note:** The discovered topic from the discussion should be passed to the initialization step, so it doesn't need to ask "What do you want to research?" again - it can focus on refining the scope for domain research.
**✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`**

View File

@ -3,4 +3,94 @@ name: bmad-market-research
description: 'Conduct market research on competition and customers. Use when the user says they need market research'
---
Follow the instructions in ./workflow.md.
# Market Research Workflow
**Goal:** Conduct comprehensive market research using current web data and verified sources to produce complete research documents with compelling narratives and proper citations.
**Your Role:** You are a market research facilitator working with an expert partner. This is a collaboration where you bring research methodology and web search capabilities, while your partner brings domain knowledge and research direction.
## Conventions
- Bare paths (e.g. `steps/step-01-init.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## PREREQUISITE
**⛔ Web search required.** If unavailable, abort and tell the user.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## QUICK TOPIC DISCOVERY
"Welcome {{user_name}}! Let's get started with your **market research**.
**What topic, problem, or area do you want to research?**
For example:
- 'The electric vehicle market in Europe'
- 'Plant-based food alternatives market'
- 'Mobile payment solutions in Southeast Asia'
- 'Or anything else you have in mind...'"
### Topic Clarification
Based on the user's topic, briefly clarify:
1. **Core Topic**: "What exactly about [topic] are you most interested in?"
2. **Research Goals**: "What do you hope to achieve with this research?"
3. **Scope**: "Should we focus broadly or dive deep into specific aspects?"
## ROUTE TO MARKET RESEARCH STEPS
After gathering the topic and goals:
1. Set `research_type = "market"`
2. Set `research_topic = [discovered topic from discussion]`
3. Set `research_goals = [discovered goals from discussion]`
4. Derive `research_topic_slug` from `{{research_topic}}`: lowercase, trim, replace whitespace with `-`, strip path separators (`/`, `\`), `..`, and any character that is not alphanumeric, `-`, or `_`. Collapse repeated `-` and strip leading/trailing `-`. If the result is empty, use `untitled`.
5. Create the starter output file: `{planning_artifacts}/research/market-{{research_topic_slug}}-research-{{date}}.md` with exact copy of the `./research.template.md` contents
6. Load: `./steps/step-01-init.md` with topic context
**Note:** The discovered topic from the discussion should be passed to the initialization step, so it doesn't need to ask "What do you want to research?" again - it can focus on refining the scope for market research.
**✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`**

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-market-research. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All briefs must include a regulatory-risk section."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches its terminal stage (Step 6: Research Completion),
# after the market research document has been saved and the user selects [C] Complete.
# Override wins. Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -475,4 +475,10 @@ Comprehensive market research workflow complete. User may:
- Combine market research with other research types for comprehensive insights
- Move forward with implementation based on strategic market recommendations
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
Congratulations on completing comprehensive market research with professional documentation! 🎉

View File

@ -1,51 +0,0 @@
# Market Research Workflow
**Goal:** Conduct comprehensive market research using current web data and verified sources to produce complete research documents with compelling narratives and proper citations.
**Your Role:** You are a market research facilitator working with an expert partner. This is a collaboration where you bring research methodology and web search capabilities, while your partner brings domain knowledge and research direction.
## PREREQUISITE
**⛔ Web search required.** If unavailable, abort and tell the user.
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
## QUICK TOPIC DISCOVERY
"Welcome {{user_name}}! Let's get started with your **market research**.
**What topic, problem, or area do you want to research?**
For example:
- 'The electric vehicle market in Europe'
- 'Plant-based food alternatives market'
- 'Mobile payment solutions in Southeast Asia'
- 'Or anything else you have in mind...'"
### Topic Clarification
Based on the user's topic, briefly clarify:
1. **Core Topic**: "What exactly about [topic] are you most interested in?"
2. **Research Goals**: "What do you hope to achieve with this research?"
3. **Scope**: "Should we focus broadly or dive deep into specific aspects?"
## ROUTE TO MARKET RESEARCH STEPS
After gathering the topic and goals:
1. Set `research_type = "market"`
2. Set `research_topic = [discovered topic from discussion]`
3. Set `research_goals = [discovered goals from discussion]`
4. Create the starter output file: `{planning_artifacts}/research/market-{{research_topic}}-research-{{date}}.md` with exact copy of the `./research.template.md` contents
5. Load: `./steps/step-01-init.md` with topic context
**Note:** The discovered topic from the discussion should be passed to the initialization step, so it doesn't need to ask "What do you want to research?" again - it can focus on refining the scope for market research.
**✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`**

View File

@ -3,4 +3,94 @@ name: bmad-technical-research
description: 'Conduct technical research on technologies and architecture. Use when the user says they would like to do or produce a technical research report'
---
Follow the instructions in ./workflow.md.
# Technical Research Workflow
**Goal:** Conduct comprehensive technical research using current web data and verified sources to produce complete research documents with compelling narratives and proper citations.
**Your Role:** You are a technical research facilitator working with an expert partner. This is a collaboration where you bring research methodology and web search capabilities, while your partner brings domain knowledge and research direction.
## Conventions
- Bare paths (e.g. `technical-steps/step-01-init.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## PREREQUISITE
**⛔ Web search required.** If unavailable, abort and tell the user.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## QUICK TOPIC DISCOVERY
"Welcome {{user_name}}! Let's get started with your **technical research**.
**What technology, tool, or technical area do you want to research?**
For example:
- 'React vs Vue for large-scale applications'
- 'GraphQL vs REST API architectures'
- 'Serverless deployment options for Node.js'
- 'Or any other technical topic you have in mind...'"
### Topic Clarification
Based on the user's topic, briefly clarify:
1. **Core Technology**: "What specific aspect of [technology] are you most interested in?"
2. **Research Goals**: "What do you hope to achieve with this research?"
3. **Scope**: "Should we focus broadly or dive deep into specific aspects?"
## ROUTE TO TECHNICAL RESEARCH STEPS
After gathering the topic and goals:
1. Set `research_type = "technical"`
2. Set `research_topic = [discovered topic from discussion]`
3. Set `research_goals = [discovered goals from discussion]`
4. Derive `research_topic_slug` from `{{research_topic}}`: lowercase, trim, replace whitespace with `-`, strip path separators (`/`, `\`), `..`, and any character that is not alphanumeric, `-`, or `_`. Collapse repeated `-` and strip leading/trailing `-`. If the result is empty, use `untitled`.
5. Create the starter output file: `{planning_artifacts}/research/technical-{{research_topic_slug}}-research-{{date}}.md` with exact copy of the `./research.template.md` contents
6. Load: `./technical-steps/step-01-init.md` with topic context
**Note:** The discovered topic from the discussion should be passed to the initialization step, so it doesn't need to ask "What do you want to research?" again - it can focus on refining the scope for technical research.
**✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`**

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-technical-research. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All briefs must include a regulatory-risk section."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches its terminal stage (Step 6: Technical Synthesis),
# after the technical research document has been saved and the user selects [C] Complete.
# Override wins. Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -484,4 +484,10 @@ Complete authoritative technical research document on {{research_topic}} that:
- Serves as technical reference document for continued use
- Maintains highest technical research quality standards with current verification
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
Congratulations on completing comprehensive technical research with professional documentation! 🎉

View File

@ -1,52 +0,0 @@
# Technical Research Workflow
**Goal:** Conduct comprehensive technical research using current web data and verified sources to produce complete research documents with compelling narratives and proper citations.
**Your Role:** You are a technical research facilitator working with an expert partner. This is a collaboration where you bring research methodology and web search capabilities, while your partner brings domain knowledge and research direction.
## PREREQUISITE
**⛔ Web search required.** If unavailable, abort and tell the user.
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
## QUICK TOPIC DISCOVERY
"Welcome {{user_name}}! Let's get started with your **technical research**.
**What technology, tool, or technical area do you want to research?**
For example:
- 'React vs Vue for large-scale applications'
- 'GraphQL vs REST API architectures'
- 'Serverless deployment options for Node.js'
- 'Or any other technical topic you have in mind...'"
### Topic Clarification
Based on the user's topic, briefly clarify:
1. **Core Technology**: "What specific aspect of [technology] are you most interested in?"
2. **Research Goals**: "What do you hope to achieve with this research?"
3. **Scope**: "Should we focus broadly or dive deep into specific aspects?"
## ROUTE TO TECHNICAL RESEARCH STEPS
After gathering the topic and goals:
1. Set `research_type = "technical"`
2. Set `research_topic = [discovered topic from discussion]`
3. Set `research_goals = [discovered goals from discussion]`
4. Create the starter output file: `{planning_artifacts}/research/technical-{{research_topic}}-research-{{date}}.md` with exact copy of the `./research.template.md` contents
5. Load: `./technical-steps/step-01-init.md` with topic context
**Note:** The discovered topic from the discussion should be passed to the initialization step, so it doesn't need to ask "What do you want to research?" again - it can focus on refining the scope for technical research.
**✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`**

View File

@ -3,4 +3,102 @@ name: bmad-create-prd
description: 'Create a PRD from scratch. Use when the user says "lets create a product requirements document" or "I want to create a new PRD"'
---
Follow the instructions in ./workflow.md.
# PRD Create Workflow
**Goal:** Create comprehensive PRDs through structured workflow facilitation.
**Your Role:** Product-focused PM facilitator collaborating with an expert peer.
You will continue to operate with your given name, identity, and communication_style, merged with the details of this role description.
## Conventions
- Bare paths (e.g. `steps-c/step-01-init.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## WORKFLOW ARCHITECTURE
This uses **step-file architecture** for disciplined execution:
### Core Principles
- **Micro-file Design**: Each step is a self-contained instruction file that is a part of an overall workflow that must be followed exactly
- **Just-In-Time Loading**: Only the current step file is in memory - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Paths
- `outputFile` = `{planning_artifacts}/prd.md`
## Execution
✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the configured `{communication_language}`.
✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`.
**Create Mode: Creating a new PRD from scratch.**
Read fully and follow: `./steps-c/step-01-init.md`

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-create-prd. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All PRDs must include a regulatory-risk section."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 12 (Workflow Completion),
# after the PRD is finalized and workflow status is updated. Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -113,3 +113,9 @@ PRD complete. Invoke the `bmad-help` skill.
The polished PRD serves as the foundation for all subsequent product development activities. All design, architecture, and development work should trace back to the requirements and vision documented in this PRD - update it also as needed as you continue planning.
**Congratulations on completing the Product Requirements Document for {{project_name}}!** 🎉
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.

View File

@ -1,61 +0,0 @@
---
main_config: '{project-root}/_bmad/bmm/config.yaml'
outputFile: '{planning_artifacts}/prd.md'
---
# PRD Create Workflow
**Goal:** Create comprehensive PRDs through structured workflow facilitation.
**Your Role:** Product-focused PM facilitator collaborating with an expert peer.
You will continue to operate with your given name, identity, and communication_style, merged with the details of this role description.
## WORKFLOW ARCHITECTURE
This uses **step-file architecture** for disciplined execution:
### Core Principles
- **Micro-file Design**: Each step is a self contained instruction file that is a part of an overall workflow that must be followed exactly
- **Just-In-Time Loading**: Only the current step file is in memory - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the configured `{communication_language}`.
✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`.
2. Route to Create Workflow
"**Create Mode: Creating a new PRD from scratch.**"
Read fully and follow: `./steps-c/step-01-init.md`

View File

@ -3,4 +3,73 @@ name: bmad-create-ux-design
description: 'Plan UX patterns and design specifications. Use when the user says "lets create UX design" or "create UX specifications" or "help me plan the UX"'
---
Follow the instructions in ./workflow.md.
# Create UX Design Workflow
**Goal:** Create comprehensive UX design specifications through collaborative visual exploration and informed decision-making where you act as a UX facilitator working with a product stakeholder.
## Conventions
- Bare paths (e.g. `steps/step-01-init.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## WORKFLOW ARCHITECTURE
This uses **micro-file architecture** for disciplined execution:
- Each step is a self-contained file with embedded rules
- Sequential progression with user control at each step
- Document state tracked in frontmatter
- Append-only document building through conversation
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Paths
- `default_output_file` = `{planning_artifacts}/ux-design-specification.md`
## EXECUTION
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
- ✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`
- Read fully and follow: `./steps/step-01-init.md` to begin the UX design workflow.

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-create-ux-design. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All designs must meet WCAG 2.1 AA accessibility standards."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 14 (Workflow Completion),
# after the UX design specification is finalized and status is updated. Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -169,3 +169,9 @@ This UX design workflow is now complete. The specification serves as the foundat
- ✅ UX Design Specification: `{planning_artifacts}/ux-design-specification.md`
- ✅ Color Themes Visualizer: `{planning_artifacts}/ux-color-themes.html`
- ✅ Design Directions: `{planning_artifacts}/ux-design-directions.html`
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.

View File

@ -1,35 +0,0 @@
# Create UX Design Workflow
**Goal:** Create comprehensive UX design specifications through collaborative visual exploration and informed decision-making where you act as a UX facilitator working with a product stakeholder.
---
## WORKFLOW ARCHITECTURE
This uses **micro-file architecture** for disciplined execution:
- Each step is a self-contained file with embedded rules
- Sequential progression with user control at each step
- Document state tracked in frontmatter
- Append-only document building through conversation
---
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Paths
- `default_output_file` = `{planning_artifacts}/ux-design-specification.md`
## EXECUTION
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
- ✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`
- Read fully and follow: `./steps/step-01-init.md` to begin the UX design workflow.

View File

@ -3,4 +3,100 @@ name: bmad-edit-prd
description: 'Edit an existing PRD. Use when the user says "edit this PRD".'
---
Follow the instructions in ./workflow.md.
# PRD Edit Workflow
**Goal:** Edit and improve existing PRDs through structured enhancement workflow.
**Your Role:** PRD improvement specialist.
You will continue to operate with your given name, identity, and communication_style, merged with the details of this role description.
## Conventions
- Bare paths (e.g. `steps-e/step-e-01-discovery.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## WORKFLOW ARCHITECTURE
This uses **step-file architecture** for disciplined execution:
### Core Principles
- **Micro-file Design**: Each step is a self-contained instruction file that is a part of an overall workflow that must be followed exactly
- **Just-In-Time Loading**: Only the current step file is in memory - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Execution
✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the configured `{communication_language}`.
✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`.
**Edit Mode: Improving an existing PRD.**
Prompt for PRD path: "Which PRD would you like to edit? Please provide the path to the PRD.md file."
Then read fully and follow: `./steps-e/step-e-01-discovery.md`

View File

@ -0,0 +1,42 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-edit-prd. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All PRDs must include a regulatory-risk section."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step E-4 (Complete & Validate) and the
# user exits via [S] Summary or [X] Exit — not on [V] Validate (which chains to
# bmad-validate-prd) or [E] Edit More (which loops back). Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -130,11 +130,13 @@ Display:
- Before/after comparison (key improvements)
- Recommendations for next steps
- Display: "**Edit Workflow Complete**"
- Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.
- Exit
- **IF X (Exit):**
- Display summary
- Display: "**Edit Workflow Complete**"
- Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.
- Exit
- **IF Any other:** Help user, then redisplay menu

View File

@ -1,62 +0,0 @@
---
main_config: '{project-root}/_bmad/bmm/config.yaml'
---
# PRD Edit Workflow
**Goal:** Edit and improve existing PRDs through structured enhancement workflow.
**Your Role:** PRD improvement specialist.
You will continue to operate with your given name, identity, and communication_style, merged with the details of this role description.
## WORKFLOW ARCHITECTURE
This uses **step-file architecture** for disciplined execution:
### Core Principles
- **Micro-file Design**: Each step is a self contained instruction file that is a part of an overall workflow that must be followed exactly
- **Just-In-Time Loading**: Only the current step file is in memory - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the configured `{communication_language}`.
✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`.
2. Route to Edit Workflow
"**Edit Mode: Improving an existing PRD.**"
Prompt for PRD path: "Which PRD would you like to edit? Please provide the path to the PRD.md file."
Then read fully and follow: `./steps-e/step-e-01-discovery.md`

View File

@ -3,4 +3,102 @@ name: bmad-validate-prd
description: 'Validate a PRD against standards. Use when the user says "validate this PRD" or "run PRD validation"'
---
Follow the instructions in ./workflow.md.
# PRD Validate Workflow
**Goal:** Validate existing PRDs against BMAD standards through comprehensive review.
**Your Role:** Validation Architect and Quality Assurance Specialist.
You will continue to operate with your given name, identity, and communication_style, merged with the details of this role description.
## Conventions
- Bare paths (e.g. `steps-v/step-v-01-discovery.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## WORKFLOW ARCHITECTURE
This uses **step-file architecture** for disciplined execution:
### Core Principles
- **Micro-file Design**: Each step is a self-contained instruction file that is a part of an overall workflow that must be followed exactly
- **Just-In-Time Loading**: Only the current step file is in memory - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Paths
- `validateWorkflow` = `./steps-v/step-v-01-discovery.md`
## Execution
✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the configured `{communication_language}`.
✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`.
**Validate Mode: Validating an existing PRD against BMAD standards.**
Then read fully and follow: `{validateWorkflow}` (steps-v/step-v-01-discovery.md)

View File

@ -0,0 +1,42 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-validate-prd. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All PRDs must include a regulatory-risk section."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 13 (Validation Report Complete) and
# the user exits via [X] Exit — not on [E] Use Edit Workflow (which chains to
# bmad-edit-prd), [R] Review (which loops within), or [F] Fix (which loops within).
# Override wins. Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -196,6 +196,7 @@ Display:
- Display: "**Validation Report Saved:** {validationReportPath}"
- Display: "**Summary:** {overall status} - {recommendation}"
- PRD Validation complete. Invoke the `bmad-help` skill.
- Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.
- **IF Any other:** Help user, then redisplay menu

View File

@ -1,61 +0,0 @@
---
main_config: '{project-root}/_bmad/bmm/config.yaml'
validateWorkflow: './steps-v/step-v-01-discovery.md'
---
# PRD Validate Workflow
**Goal:** Validate existing PRDs against BMAD standards through comprehensive review.
**Your Role:** Validation Architect and Quality Assurance Specialist.
You will continue to operate with your given name, identity, and communication_style, merged with the details of this role description.
## WORKFLOW ARCHITECTURE
This uses **step-file architecture** for disciplined execution:
### Core Principles
- **Micro-file Design**: Each step is a self contained instruction file that is a part of an overall workflow that must be followed exactly
- **Just-In-Time Loading**: Only the current step file is in memory - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the configured `{communication_language}`.
✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`.
2. Route to Validate Workflow
"**Validate Mode: Validating an existing PRD against BMAD standards.**"
Then read fully and follow: `{validateWorkflow}` (steps-v/step-v-01-discovery.md)

View File

@ -3,4 +3,89 @@ name: bmad-check-implementation-readiness
description: 'Validate PRD, UX, Architecture and Epics specs are complete. Use when the user says "check implementation readiness".'
---
Follow the instructions in ./workflow.md.
# Implementation Readiness
**Goal:** Validate that PRD, UX, Architecture, Epics and Stories are complete and aligned before Phase 4 implementation starts, with a focus on ensuring epics and stories are logical and have accounted for all requirements and planning.
**Your Role:** You are an expert Product Manager, renowned and respected in the field of requirements traceability and spotting gaps in planning. Your success is measured in spotting the failures others have made in planning or preparation of epics and stories to produce the user's product vision.
## Conventions
- Bare paths (e.g. `steps/step-01-document-discovery.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## WORKFLOW ARCHITECTURE
### Core Principles
- **Micro-file Design**: Each step toward the overall goal is a self-contained instruction file; adhere to one file at a time, as directed
- **Just-In-Time Loading**: Only 1 current step file will be loaded and followed to completion - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Execution
Read fully and follow: `./steps/step-01-document-discovery.md` to begin the workflow.

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-check-implementation-readiness. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All artifacts must follow org naming conventions."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 6 (Final Assessment),
# after the readiness report has been saved and presented. Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -124,3 +124,9 @@ Implementation Readiness complete. Invoke the `bmad-help` skill.
- Not reviewing previous findings
- Incomplete summary
- No clear recommendations
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.

View File

@ -1,47 +0,0 @@
# Implementation Readiness
**Goal:** Validate that PRD, Architecture, Epics and Stories are complete and aligned before Phase 4 implementation starts, with a focus on ensuring epics and stories are logical and have accounted for all requirements and planning.
**Your Role:** You are an expert Product Manager, renowned and respected in the field of requirements traceability and spotting gaps in planning. Your success is measured in spotting the failures others have made in planning or preparation of epics and stories to produce the user's product vision.
## WORKFLOW ARCHITECTURE
### Core Principles
- **Micro-file Design**: Each step of the overall goal is a self contained instruction file that you will adhere too 1 file as directed at a time
- **Just-In-Time Loading**: Only 1 current step file will be loaded and followed to completion - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
2. First Step EXECUTION
Read fully and follow: `./steps/step-01-document-discovery.md` to begin the workflow.

View File

@ -3,4 +3,72 @@ name: bmad-create-architecture
description: 'Create architecture solution design decisions for AI agent consistency. Use when the user says "lets create architecture" or "create technical architecture" or "create a solution design"'
---
Follow the instructions in ./workflow.md.
# Architecture Workflow
**Goal:** Create comprehensive architecture decisions through collaborative step-by-step discovery that ensures AI agents implement consistently.
**Your Role:** You are an architectural facilitator collaborating with a peer. This is a partnership, not a client-vendor relationship. You bring structured thinking and architectural knowledge, while the user brings domain expertise and product vision. Work together as equals to make decisions that prevent implementation conflicts.
## Conventions
- Bare paths (e.g. `steps/step-01-init.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## WORKFLOW ARCHITECTURE
This uses **micro-file architecture** for disciplined execution:
- Each step is a self-contained file with embedded rules
- Sequential progression with user control at each step
- Document state tracked in frontmatter
- Append-only document building through conversation
- You NEVER proceed to a step file if the current step file indicates the user must approve and indicate continuation.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Execution
Read fully and follow: `./steps/step-01-init.md` to begin the workflow.
**Note:** Input document discovery and all initialization protocols are handled in step-01-init.md.

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-create-architecture. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "Our org is AWS-only -- do not propose GCP or Azure."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 8 (Architecture Completion & Handoff),
# after the architecture document frontmatter is updated and next-steps guidance is given.
# Override wins. Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -74,3 +74,9 @@ Upon Completion of task output: offer to answer any questions about the Architec
This is the final step of the Architecture workflow. The user now has a complete, validated architecture document ready for AI agent implementation.
The architecture will serve as the single source of truth for all technical decisions, ensuring consistent implementation across the entire project development lifecycle.
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.

View File

@ -1,32 +0,0 @@
# Architecture Workflow
**Goal:** Create comprehensive architecture decisions through collaborative step-by-step discovery that ensures AI agents implement consistently.
**Your Role:** You are an architectural facilitator collaborating with a peer. This is a partnership, not a client-vendor relationship. You bring structured thinking and architectural knowledge, while the user brings domain expertise and product vision. Work together as equals to make decisions that prevent implementation conflicts.
---
## WORKFLOW ARCHITECTURE
This uses **micro-file architecture** for disciplined execution:
- Each step is a self-contained file with embedded rules
- Sequential progression with user control at each step
- Document state tracked in frontmatter
- Append-only document building through conversation
- You NEVER proceed to a step file if the current step file indicates the user must approve and indicate continuation.
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
2. EXECUTION
Read fully and follow: `./steps/step-01-init.md` to begin the workflow.
**Note:** Input document discovery and all initialization protocols are handled in step-01-init.md.

View File

@ -3,4 +3,91 @@ name: bmad-create-epics-and-stories
description: 'Break requirements into epics and user stories. Use when the user says "create the epics and stories list"'
---
Follow the instructions in ./workflow.md.
# Create Epics and Stories
**Goal:** Transform PRD requirements and Architecture decisions into comprehensive stories organized by user value, creating detailed, actionable stories with complete acceptance criteria for the Developer agent.
**Your Role:** In addition to your name, communication_style, and persona, you are also a product strategist and technical specifications writer collaborating with a product owner. This is a partnership, not a client-vendor relationship. You bring expertise in requirements decomposition, technical implementation context, and acceptance criteria writing, while the user brings their product vision, user needs, and business requirements. Work together as equals.
## Conventions
- Bare paths (e.g. `steps/step-01-validate-prerequisites.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## WORKFLOW ARCHITECTURE
This uses **step-file architecture** for disciplined execution:
### Core Principles
- **Micro-file Design**: Each step toward the overall goal is a self-contained instruction file; adhere to one file at a time, as directed
- **Just-In-Time Loading**: Only 1 current step file will be loaded and followed to completion - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Execution
Read fully and follow: `./steps/step-01-validate-prerequisites.md` to begin the workflow.

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-create-epics-and-stories. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All epics must deliver complete end-to-end user value."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 4 (Final Validation) and the
# user confirms [C] Complete — after the epics.md is saved and bmad-help is invoked.
# Override wins. Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -129,3 +129,9 @@ When C is selected, the workflow is complete and the epics.md is ready for devel
Epics and Stories complete. Invoke the `bmad-help` skill.
Upon Completion of task output: offer to answer any questions about the Epics and Stories.
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.

View File

@ -1,51 +0,0 @@
# Create Epics and Stories
**Goal:** Transform PRD requirements and Architecture decisions into comprehensive stories organized by user value, creating detailed, actionable stories with complete acceptance criteria for the Developer agent.
**Your Role:** In addition to your name, communication_style, and persona, you are also a product strategist and technical specifications writer collaborating with a product owner. This is a partnership, not a client-vendor relationship. You bring expertise in requirements decomposition, technical implementation context, and acceptance criteria writing, while the user brings their product vision, user needs, and business requirements. Work together as equals.
---
## WORKFLOW ARCHITECTURE
This uses **step-file architecture** for disciplined execution:
### Core Principles
- **Micro-file Design**: Each step of the overall goal is a self contained instruction file that you will adhere too 1 file as directed at a time
- **Just-In-Time Loading**: Only 1 current step file will be loaded and followed to completion - never load future step files until told to do so
- **Sequential Enforcement**: Sequence within the step files must be completed in order, no skipping or optimization allowed
- **State Tracking**: Document progress in output file frontmatter using `stepsCompleted` array when a workflow produces a document
- **Append-Only Building**: Build documents by appending content as directed to the output file
### Step Processing Rules
1. **READ COMPLETELY**: Always read the entire step file before taking any action
2. **FOLLOW SEQUENCE**: Execute all numbered sections in order, never deviate
3. **WAIT FOR INPUT**: If a menu is presented, halt and wait for user selection
4. **CHECK CONTINUATION**: If the step has a menu with Continue as an option, only proceed to next step when user selects 'C' (Continue)
5. **SAVE STATE**: Update `stepsCompleted` in frontmatter before loading next step
6. **LOAD NEXT**: When directed, read fully and follow the next step file
### Critical Rules (NO EXCEPTIONS)
- 🛑 **NEVER** load multiple step files simultaneously
- 📖 **ALWAYS** read entire step file before execution
- 🚫 **NEVER** skip steps or optimize the sequence
- 💾 **ALWAYS** update frontmatter of output files when writing the final output for a specific step
- 🎯 **ALWAYS** follow the exact instructions in the step file
- ⏸️ **ALWAYS** halt at menus and wait for user input
- 📋 **NEVER** create mental todo lists from future steps
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
2. First Step EXECUTION
Read fully and follow: `./steps/step-01-validate-prerequisites.md` to begin the workflow.

View File

@ -3,4 +3,79 @@ name: bmad-generate-project-context
description: 'Create project-context.md with AI rules. Use when the user says "generate project context" or "create project context"'
---
Follow the instructions in ./workflow.md.
# Generate Project Context Workflow
**Goal:** Create a concise, optimized `project-context.md` file containing critical rules, patterns, and guidelines that AI agents must follow when implementing code. This file focuses on unobvious details that LLMs need to be reminded of.
**Your Role:** You are a technical facilitator working with a peer to capture the essential implementation rules that will ensure consistent, high-quality code generation across all AI agents working on the project.
## Conventions
- Bare paths (e.g. `steps/step-01-discover.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## WORKFLOW ARCHITECTURE
This uses **micro-file architecture** for disciplined execution:
- Each step is a self-contained file with embedded rules
- Sequential progression with user control at each step
- Document state tracked in frontmatter
- Focus on lean, LLM-optimized content generation
- You NEVER proceed to a step file if the current step file indicates the user must approve and indicate continuation.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Paths
- `output_file` = `{output_folder}/project-context.md`
## Execution
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
- ✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`
Load and execute `./steps/step-01-discover.md` to begin the workflow.
**Note:** Input document discovery and initialization protocols are handled in step-01-discover.md.

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-generate-project-context. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All artifacts must follow org naming conventions."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 3 (Context Completion & Finalization),
# after the project-context.md file is optimized and saved. Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -276,3 +276,9 @@ Your project context will help ensure high-quality, consistent implementation ac
This is the final step of the Generate Project Context workflow. The user now has a comprehensive, optimized project context file that will ensure consistent, high-quality implementation across all AI agents working on the project.
The project context file serves as the critical "rules of the road" that agents need to implement code consistently with the project's standards and patterns.
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.

View File

@ -1,39 +0,0 @@
# Generate Project Context Workflow
**Goal:** Create a concise, optimized `project-context.md` file containing critical rules, patterns, and guidelines that AI agents must follow when implementing code. This file focuses on unobvious details that LLMs need to be reminded of.
**Your Role:** You are a technical facilitator working with a peer to capture the essential implementation rules that will ensure consistent, high-quality code generation across all AI agents working on the project.
---
## WORKFLOW ARCHITECTURE
This uses **micro-file architecture** for disciplined execution:
- Each step is a self-contained file with embedded rules
- Sequential progression with user control at each step
- Document state tracked in frontmatter
- Focus on lean, LLM-optimized content generation
- You NEVER proceed to a step file if the current step file indicates the user must approve and indicate continuation.
---
## Activation
1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
- Use `{user_name}` for greeting
- Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`
- ✅ YOU MUST ALWAYS WRITE all artifact and document content in `{document_output_language}`
- `output_file` = `{output_folder}/project-context.md`
EXECUTION
Load and execute `./steps/step-01-discover.md` to begin the workflow.
**Note:** Input document discovery and initialization protocols are handled in step-01-discover.md.

View File

@ -18,7 +18,7 @@ If zero findings remain after triage (all dismissed or none raised): state that
### 2. Write findings to the story file
If `{spec_file}` exists and contains a Tasks/Subtasks section, append a `### Review Findings` subsection. Write all findings in this order:
If `{spec_file}` exists and contains a Tasks/Subtasks section, append a `### Review Follow-ups (AI)` subsection if not exists already. Append all findings in this order:
1. **`decision-needed`** findings (unchecked):
`- [ ] [Review][Decision] <Title> — <Detail>`

View File

@ -3,4 +3,299 @@ name: bmad-correct-course
description: 'Manage significant changes during sprint execution. Use when the user says "correct course" or "propose sprint change"'
---
Follow the instructions in ./workflow.md.
# Correct Course - Sprint Change Management Workflow
**Goal:** Manage significant changes during sprint execution by analyzing impact across all project artifacts and producing a structured Sprint Change Proposal.
**Your Role:** You are a Developer navigating change management. Analyze the triggering issue, assess impact across PRD, epics, architecture, and UX artifacts, and produce an actionable Sprint Change Proposal with clear handoff.
## Conventions
- Bare paths (e.g. `checklist.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `user_name`
- `communication_language`, `document_output_language`
- `user_skill_level`
- `implementation_artifacts`
- `planning_artifacts`
- `project_knowledge`
- `date` as system-generated current datetime
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
- Language MUST be tailored to `{user_skill_level}`
- Generate all documents in `{document_output_language}`
- DOCUMENT OUTPUT: Updated epics, stories, or PRD sections. Clear, actionable changes. User skill level (`{user_skill_level}`) affects conversation style ONLY, not document updates.
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Paths
- `default_output_file` = `{planning_artifacts}/sprint-change-proposal-{date}.md`
## Input Files
| Input | Path | Load Strategy |
|-------|------|---------------|
| PRD | `{planning_artifacts}/*prd*.md` (whole) or `{planning_artifacts}/*prd*/*.md` (sharded) | FULL_LOAD |
| Epics | `{planning_artifacts}/*epic*.md` (whole) or `{planning_artifacts}/*epic*/*.md` (sharded) | FULL_LOAD |
| Architecture | `{planning_artifacts}/*architecture*.md` (whole) or `{planning_artifacts}/*architecture*/*.md` (sharded) | FULL_LOAD |
| UX Design | `{planning_artifacts}/*ux*.md` (whole) or `{planning_artifacts}/*ux*/*.md` (sharded) | FULL_LOAD |
| Spec | `{planning_artifacts}/*spec-*.md` (whole) | FULL_LOAD |
| Document Project | `{project_knowledge}/index.md` (sharded) | INDEX_GUIDED |
## Execution
### Document Discovery - Loading Project Artifacts
**Strategy**: Course correction needs broad project context to assess change impact accurately. Load all available planning artifacts.
**Discovery Process for FULL_LOAD documents (PRD, Epics, Architecture, UX Design, Spec):**
1. **Search for whole document first** - Look for files matching the whole-document pattern (e.g., `*prd*.md`, `*epic*.md`, `*architecture*.md`, `*ux*.md`, `*spec-*.md`)
2. **Check for sharded version** - If whole document not found, look for a directory with `index.md` (e.g., `prd/index.md`, `epics/index.md`)
3. **If sharded version found**:
- Read `index.md` to understand the document structure
- Read ALL section files listed in the index
- Process the combined content as a single document
4. **Priority**: If both whole and sharded versions exist, use the whole document
**Discovery Process for INDEX_GUIDED documents (Document Project):**
1. **Search for index file** - Look for `{project_knowledge}/index.md`
2. **If found**: Read the index to understand available documentation sections
3. **Selectively load sections** based on relevance to the change being analyzed — do NOT load everything, only sections that relate to the impacted areas
4. **This document is optional** — skip if `{project_knowledge}` does not exist (greenfield projects)
**Fuzzy matching**: Be flexible with document names — users may use variations like `prd.md`, `bmm-prd.md`, `product-requirements.md`, etc.
**Missing documents**: Not all documents may exist. PRD and Epics are essential; Architecture, UX Design, Spec, and Document Project are loaded if available. HALT if PRD or Epics cannot be found.
<workflow>
<step n="1" goal="Initialize Change Navigation">
<action>Confirm change trigger and gather user description of the issue</action>
<action>Ask: "What specific issue or change has been identified that requires navigation?"</action>
<action>Verify access to project documents:</action>
- PRD (Product Requirements Document) — required
- Current Epics and Stories — required
- Architecture documentation — optional, load if available
- UI/UX specifications — optional, load if available
<action>Ask user for mode preference:</action>
- **Incremental** (recommended): Refine each edit collaboratively
- **Batch**: Present all changes at once for review
<action>Store mode selection for use throughout workflow</action>
<action if="change trigger is unclear">HALT: "Cannot navigate change without clear understanding of the triggering issue. Please provide specific details about what needs to change and why."</action>
<action if="PRD or Epics are unavailable">HALT: "Need access to PRD and Epics to assess change impact. Please ensure these documents are accessible. Architecture and UI/UX will be used if available."</action>
</step>
<step n="2" goal="Execute Change Analysis Checklist">
<action>Read fully and follow the systematic analysis from: checklist.md</action>
<action>Work through each checklist section interactively with the user</action>
<action>Record status for each checklist item:</action>
- [x] Done - Item completed successfully
- [N/A] Skip - Item not applicable to this change
- [!] Action-needed - Item requires attention or follow-up
<action>Maintain running notes of findings and impacts discovered</action>
<action>Present checklist progress after each major section</action>
<action if="checklist cannot be completed">Identify blocking issues and work with user to resolve before continuing</action>
</step>
<step n="3" goal="Draft Specific Change Proposals">
<action>Based on checklist findings, create explicit edit proposals for each identified artifact</action>
<action>For Story changes:</action>
- Show old → new text format
- Include story ID and section being modified
- Provide rationale for each change
- Example format:
```
Story: [STORY-123] User Authentication
Section: Acceptance Criteria
OLD:
- User can log in with email/password
NEW:
- User can log in with email/password
- User can enable 2FA via authenticator app
Rationale: Security requirement identified during implementation
```
<action>For PRD modifications:</action>
- Specify exact sections to update
- Show current content and proposed changes
- Explain impact on MVP scope and requirements
<action>For Architecture changes:</action>
- Identify affected components, patterns, or technology choices
- Describe diagram updates needed
- Note any ripple effects on other components
<action>For UI/UX specification updates:</action>
- Reference specific screens or components
- Show wireframe or flow changes needed
- Connect changes to user experience impact
<check if="mode is Incremental">
<action>Present each edit proposal individually</action>
<ask>Review and refine this change? Options: Approve [a], Edit [e], Skip [s]</ask>
<action>Iterate on each proposal based on user feedback</action>
</check>
<action if="mode is Batch">Collect all edit proposals and present together at end of step</action>
</step>
<step n="4" goal="Generate Sprint Change Proposal">
<action>Compile comprehensive Sprint Change Proposal document with following sections:</action>
<action>Section 1: Issue Summary</action>
- Clear problem statement describing what triggered the change
- Context about when/how the issue was discovered
- Evidence or examples demonstrating the issue
<action>Section 2: Impact Analysis</action>
- Epic Impact: Which epics are affected and how
- Story Impact: Current and future stories requiring changes
- Artifact Conflicts: PRD, Architecture, UI/UX documents needing updates
- Technical Impact: Code, infrastructure, or deployment implications
<action>Section 3: Recommended Approach</action>
- Present chosen path forward from checklist evaluation:
- Direct Adjustment: Modify/add stories within existing plan
- Potential Rollback: Revert completed work to simplify resolution
- MVP Review: Reduce scope or modify goals
- Provide clear rationale for recommendation
- Include effort estimate, risk assessment, and timeline impact
<action>Section 4: Detailed Change Proposals</action>
- Include all refined edit proposals from Step 3
- Group by artifact type (Stories, PRD, Architecture, UI/UX)
- Ensure each change includes before/after and justification
<action>Section 5: Implementation Handoff</action>
- Categorize change scope:
- Minor: Direct implementation by Developer agent
- Moderate: Backlog reorganization needed (PO/DEV)
- Major: Fundamental replan required (PM/Architect)
- Specify handoff recipients and their responsibilities
- Define success criteria for implementation
<action>Present complete Sprint Change Proposal to user</action>
<action>Write Sprint Change Proposal document to {default_output_file}</action>
<ask>Review complete proposal. Continue [c] or Edit [e]?</ask>
</step>
<step n="5" goal="Finalize and Route for Implementation">
<action>Get explicit user approval for complete proposal</action>
<ask>Do you approve this Sprint Change Proposal for implementation? (yes/no/revise)</ask>
<check if="no or revise">
<action>Gather specific feedback on what needs adjustment</action>
<action>Return to appropriate step to address concerns</action>
<goto step="3">If changes needed to edit proposals</goto>
<goto step="4">If changes needed to overall proposal structure</goto>
</check>
<check if="yes the proposal is approved by the user">
<action>Finalize Sprint Change Proposal document</action>
<action>Determine change scope classification:</action>
- **Minor**: Can be implemented directly by Developer agent
- **Moderate**: Requires backlog reorganization and PO/DEV coordination
- **Major**: Needs fundamental replan with PM/Architect involvement
<action>Provide appropriate handoff based on scope:</action>
</check>
<check if="Minor scope">
<action>Route to: Developer agent for direct implementation</action>
<action>Deliverables: Finalized edit proposals and implementation tasks</action>
</check>
<check if="Moderate scope">
<action>Route to: Product Owner / Developer agents</action>
<action>Deliverables: Sprint Change Proposal + backlog reorganization plan</action>
</check>
<check if="Major scope">
<action>Route to: Product Manager / Solution Architect</action>
<action>Deliverables: Complete Sprint Change Proposal + escalation notice</action>
<action>Confirm handoff completion and next steps with user</action>
<action>Document handoff in workflow execution log</action>
</check>
</step>
<step n="6" goal="Workflow Completion">
<action>Summarize workflow execution:</action>
- Issue addressed: {{change_trigger}}
- Change scope: {{scope_classification}}
- Artifacts modified: {{list_of_artifacts}}
- Routed to: {{handoff_recipients}}
<action>Confirm all deliverables produced:</action>
- Sprint Change Proposal document
- Specific edit proposals with before/after
- Implementation handoff plan
<action>Report workflow completion to user with personalized message: "Correct Course workflow complete, {user_name}!"</action>
<action>Remind user of success criteria and next steps for Developer agent</action>
<action>Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
</step>
</workflow>

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-correct-course. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All sprint changes require PO sign-off before execution."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 6 (Workflow Completion),
# after the Sprint Change Proposal is finalized and handoff is confirmed. Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -1,267 +0,0 @@
# Correct Course - Sprint Change Management Workflow
**Goal:** Manage significant changes during sprint execution by analyzing impact across all project artifacts and producing a structured Sprint Change Proposal.
**Your Role:** You are a Developer navigating change management. Analyze the triggering issue, assess impact across PRD, epics, architecture, and UX artifacts, and produce an actionable Sprint Change Proposal with clear handoff.
---
## INITIALIZATION
### Configuration Loading
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `user_name`
- `communication_language`, `document_output_language`
- `user_skill_level`
- `implementation_artifacts`
- `planning_artifacts`
- `project_knowledge`
- `date` as system-generated current datetime
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
- Language MUST be tailored to `{user_skill_level}`
- Generate all documents in `{document_output_language}`
- DOCUMENT OUTPUT: Updated epics, stories, or PRD sections. Clear, actionable changes. User skill level (`{user_skill_level}`) affects conversation style ONLY, not document updates.
### Paths
- `default_output_file` = `{planning_artifacts}/sprint-change-proposal-{date}.md`
### Input Files
| Input | Path | Load Strategy |
|-------|------|---------------|
| PRD | `{planning_artifacts}/*prd*.md` (whole) or `{planning_artifacts}/*prd*/*.md` (sharded) | FULL_LOAD |
| Epics | `{planning_artifacts}/*epic*.md` (whole) or `{planning_artifacts}/*epic*/*.md` (sharded) | FULL_LOAD |
| Architecture | `{planning_artifacts}/*architecture*.md` (whole) or `{planning_artifacts}/*architecture*/*.md` (sharded) | FULL_LOAD |
| UX Design | `{planning_artifacts}/*ux*.md` (whole) or `{planning_artifacts}/*ux*/*.md` (sharded) | FULL_LOAD |
| Spec | `{planning_artifacts}/*spec-*.md` (whole) | FULL_LOAD |
| Document Project | `{project_knowledge}/index.md` (sharded) | INDEX_GUIDED |
### Context
- Load `**/project-context.md` if it exists
---
## EXECUTION
### Document Discovery - Loading Project Artifacts
**Strategy**: Course correction needs broad project context to assess change impact accurately. Load all available planning artifacts.
**Discovery Process for FULL_LOAD documents (PRD, Epics, Architecture, UX Design, Spec):**
1. **Search for whole document first** - Look for files matching the whole-document pattern (e.g., `*prd*.md`, `*epic*.md`, `*architecture*.md`, `*ux*.md`, `*spec-*.md`)
2. **Check for sharded version** - If whole document not found, look for a directory with `index.md` (e.g., `prd/index.md`, `epics/index.md`)
3. **If sharded version found**:
- Read `index.md` to understand the document structure
- Read ALL section files listed in the index
- Process the combined content as a single document
4. **Priority**: If both whole and sharded versions exist, use the whole document
**Discovery Process for INDEX_GUIDED documents (Document Project):**
1. **Search for index file** - Look for `{project_knowledge}/index.md`
2. **If found**: Read the index to understand available documentation sections
3. **Selectively load sections** based on relevance to the change being analyzed — do NOT load everything, only sections that relate to the impacted areas
4. **This document is optional** — skip if `{project_knowledge}` does not exist (greenfield projects)
**Fuzzy matching**: Be flexible with document names — users may use variations like `prd.md`, `bmm-prd.md`, `product-requirements.md`, etc.
**Missing documents**: Not all documents may exist. PRD and Epics are essential; Architecture, UX Design, Spec, and Document Project are loaded if available. HALT if PRD or Epics cannot be found.
<workflow>
<step n="1" goal="Initialize Change Navigation">
<action>Load **/project-context.md for coding standards and project-wide patterns (if exists)</action>
<action>Confirm change trigger and gather user description of the issue</action>
<action>Ask: "What specific issue or change has been identified that requires navigation?"</action>
<action>Verify access to required project documents:</action>
- PRD (Product Requirements Document)
- Current Epics and Stories
- Architecture documentation
- UI/UX specifications
<action>Ask user for mode preference:</action>
- **Incremental** (recommended): Refine each edit collaboratively
- **Batch**: Present all changes at once for review
<action>Store mode selection for use throughout workflow</action>
<action if="change trigger is unclear">HALT: "Cannot navigate change without clear understanding of the triggering issue. Please provide specific details about what needs to change and why."</action>
<action if="core documents are unavailable">HALT: "Need access to project documents (PRD, Epics, Architecture, UI/UX) to assess change impact. Please ensure these documents are accessible."</action>
</step>
<step n="2" goal="Execute Change Analysis Checklist">
<action>Read fully and follow the systematic analysis from: checklist.md</action>
<action>Work through each checklist section interactively with the user</action>
<action>Record status for each checklist item:</action>
- [x] Done - Item completed successfully
- [N/A] Skip - Item not applicable to this change
- [!] Action-needed - Item requires attention or follow-up
<action>Maintain running notes of findings and impacts discovered</action>
<action>Present checklist progress after each major section</action>
<action if="checklist cannot be completed">Identify blocking issues and work with user to resolve before continuing</action>
</step>
<step n="3" goal="Draft Specific Change Proposals">
<action>Based on checklist findings, create explicit edit proposals for each identified artifact</action>
<action>For Story changes:</action>
- Show old → new text format
- Include story ID and section being modified
- Provide rationale for each change
- Example format:
```
Story: [STORY-123] User Authentication
Section: Acceptance Criteria
OLD:
- User can log in with email/password
NEW:
- User can log in with email/password
- User can enable 2FA via authenticator app
Rationale: Security requirement identified during implementation
```
<action>For PRD modifications:</action>
- Specify exact sections to update
- Show current content and proposed changes
- Explain impact on MVP scope and requirements
<action>For Architecture changes:</action>
- Identify affected components, patterns, or technology choices
- Describe diagram updates needed
- Note any ripple effects on other components
<action>For UI/UX specification updates:</action>
- Reference specific screens or components
- Show wireframe or flow changes needed
- Connect changes to user experience impact
<check if="mode is Incremental">
<action>Present each edit proposal individually</action>
<ask>Review and refine this change? Options: Approve [a], Edit [e], Skip [s]</ask>
<action>Iterate on each proposal based on user feedback</action>
</check>
<action if="mode is Batch">Collect all edit proposals and present together at end of step</action>
</step>
<step n="4" goal="Generate Sprint Change Proposal">
<action>Compile comprehensive Sprint Change Proposal document with following sections:</action>
<action>Section 1: Issue Summary</action>
- Clear problem statement describing what triggered the change
- Context about when/how the issue was discovered
- Evidence or examples demonstrating the issue
<action>Section 2: Impact Analysis</action>
- Epic Impact: Which epics are affected and how
- Story Impact: Current and future stories requiring changes
- Artifact Conflicts: PRD, Architecture, UI/UX documents needing updates
- Technical Impact: Code, infrastructure, or deployment implications
<action>Section 3: Recommended Approach</action>
- Present chosen path forward from checklist evaluation:
- Direct Adjustment: Modify/add stories within existing plan
- Potential Rollback: Revert completed work to simplify resolution
- MVP Review: Reduce scope or modify goals
- Provide clear rationale for recommendation
- Include effort estimate, risk assessment, and timeline impact
<action>Section 4: Detailed Change Proposals</action>
- Include all refined edit proposals from Step 3
- Group by artifact type (Stories, PRD, Architecture, UI/UX)
- Ensure each change includes before/after and justification
<action>Section 5: Implementation Handoff</action>
- Categorize change scope:
- Minor: Direct implementation by Developer agent
- Moderate: Backlog reorganization needed (PO/DEV)
- Major: Fundamental replan required (PM/Architect)
- Specify handoff recipients and their responsibilities
- Define success criteria for implementation
<action>Present complete Sprint Change Proposal to user</action>
<action>Write Sprint Change Proposal document to {default_output_file}</action>
<ask>Review complete proposal. Continue [c] or Edit [e]?</ask>
</step>
<step n="5" goal="Finalize and Route for Implementation">
<action>Get explicit user approval for complete proposal</action>
<ask>Do you approve this Sprint Change Proposal for implementation? (yes/no/revise)</ask>
<check if="no or revise">
<action>Gather specific feedback on what needs adjustment</action>
<action>Return to appropriate step to address concerns</action>
<goto step="3">If changes needed to edit proposals</goto>
<goto step="4">If changes needed to overall proposal structure</goto>
</check>
<check if="yes the proposal is approved by the user">
<action>Finalize Sprint Change Proposal document</action>
<action>Determine change scope classification:</action>
- **Minor**: Can be implemented directly by Developer agent
- **Moderate**: Requires backlog reorganization and PO/DEV coordination
- **Major**: Needs fundamental replan with PM/Architect involvement
<action>Provide appropriate handoff based on scope:</action>
</check>
<check if="Minor scope">
<action>Route to: Developer agent for direct implementation</action>
<action>Deliverables: Finalized edit proposals and implementation tasks</action>
</check>
<check if="Moderate scope">
<action>Route to: Product Owner / Developer agents</action>
<action>Deliverables: Sprint Change Proposal + backlog reorganization plan</action>
</check>
<check if="Major scope">
<action>Route to: Product Manager / Solution Architect</action>
<action>Deliverables: Complete Sprint Change Proposal + escalation notice</action>
<action>Confirm handoff completion and next steps with user</action>
<action>Document handoff in workflow execution log</action>
</check>
</step>
<step n="6" goal="Workflow Completion">
<action>Summarize workflow execution:</action>
- Issue addressed: {{change_trigger}}
- Change scope: {{scope_classification}}
- Artifacts modified: {{list_of_artifacts}}
- Routed to: {{handoff_recipients}}
<action>Confirm all deliverables produced:</action>
- Sprint Change Proposal document
- Specific edit proposals with before/after
- Implementation handoff plan
<action>Report workflow completion to user with personalized message: "Correct Course workflow complete, {user_name}!"</action>
<action>Remind user of success criteria and next steps for Developer agent</action>
</step>
</workflow>

View File

@ -3,4 +3,415 @@ name: bmad-create-story
description: 'Creates a dedicated story file with all the context the agent will need to implement it later. Use when the user says "create the next story" or "create story [story identifier]"'
---
Follow the instructions in ./workflow.md.
# Create Story Workflow
**Goal:** Create a comprehensive story file that gives the dev agent everything needed for flawless implementation.
**Your Role:** Story context engine that prevents LLM developer mistakes, omissions, or disasters.
- Communicate all responses in {communication_language} and generate all documents in {document_output_language}
- Your purpose is NOT to copy from epics - it's to create a comprehensive, optimized story file that gives the DEV agent EVERYTHING needed for flawless implementation
- COMMON LLM MISTAKES TO PREVENT: reinventing wheels, wrong libraries, wrong file locations, breaking regressions, ignoring UX, vague implementations, lying about completion, not learning from past work
- EXHAUSTIVE ANALYSIS REQUIRED: You must thoroughly analyze ALL artifacts to extract critical context - do NOT be lazy or skim! This is the most important function in the entire development process!
- UTILIZE SUBPROCESSES AND SUBAGENTS: Use research subagents, subprocesses or parallel processing if available to thoroughly analyze different artifacts simultaneously and thoroughly
- SAVE QUESTIONS: If you think of questions or clarifications during analysis, save them for the end after the complete story is written
- ZERO USER INTERVENTION: Process should be fully automated except for initial epic/story selection or missing documents
## Conventions
- Bare paths (e.g. `discover-inputs.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `user_name`
- `communication_language`, `document_output_language`
- `user_skill_level`
- `planning_artifacts`, `implementation_artifacts`
- `date` as system-generated current datetime
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Paths
- `sprint_status` = `{implementation_artifacts}/sprint-status.yaml`
- `epics_file` = `{planning_artifacts}/epics.md`
- `prd_file` = `{planning_artifacts}/prd.md`
- `architecture_file` = `{planning_artifacts}/architecture.md`
- `ux_file` = `{planning_artifacts}/*ux*.md`
- `story_title` = "" (will be elicited if not derivable)
- `default_output_file` = `{implementation_artifacts}/{{story_key}}.md`
## Input Files
| Input | Description | Path Pattern(s) | Load Strategy |
|-------|-------------|------------------|---------------|
| prd | PRD (fallback - epics file should have most content) | whole: `{planning_artifacts}/*prd*.md`, sharded: `{planning_artifacts}/*prd*/*.md` | SELECTIVE_LOAD |
| architecture | Architecture (fallback - epics file should have relevant sections) | whole: `{planning_artifacts}/*architecture*.md`, sharded: `{planning_artifacts}/*architecture*/*.md` | SELECTIVE_LOAD |
| ux | UX design (fallback - epics file should have relevant sections) | whole: `{planning_artifacts}/*ux*.md`, sharded: `{planning_artifacts}/*ux*/*.md` | SELECTIVE_LOAD |
| epics | Enhanced epics+stories file with BDD and source hints | whole: `{planning_artifacts}/*epic*.md`, sharded: `{planning_artifacts}/*epic*/*.md` | SELECTIVE_LOAD |
## Execution
<workflow>
<step n="1" goal="Determine target story">
<check if="{{story_path}} is provided by user or user provided the epic and story number such as 2-4 or 1.6 or epic 1 story 5">
<action>Parse user-provided story path: extract epic_num, story_num, story_title from format like "1-2-user-auth"</action>
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>
<action>GOTO step 2a</action>
</check>
<action>Check if {{sprint_status}} file exists for auto discover</action>
<check if="sprint status file does NOT exist">
<output>🚫 No sprint status file found and no story specified</output>
<output>
**Required Options:**
1. Run `sprint-planning` to initialize sprint tracking (recommended)
2. Provide specific epic-story number to create (e.g., "1-2-user-auth")
3. Provide path to story documents if sprint status doesn't exist yet
</output>
<ask>Choose option [1], provide epic-story number, path to story docs, or [q] to quit:</ask>
<check if="user chooses 'q'">
<action>HALT - No work needed</action>
</check>
<check if="user chooses '1'">
<output>Run sprint-planning workflow first to create sprint-status.yaml</output>
<action>HALT - User needs to run sprint-planning</action>
</check>
<check if="user provides epic-story number">
<action>Parse user input: extract epic_num, story_num, story_title</action>
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>
<action>GOTO step 2a</action>
</check>
<check if="user provides story docs path">
<action>Use user-provided path for story documents</action>
<action>GOTO step 2a</action>
</check>
</check>
<!-- Auto-discover from sprint status only if no user input -->
<check if="no user input provided">
<critical>MUST read COMPLETE {sprint_status} file from start to end to preserve order</critical>
<action>Load the FULL file: {{sprint_status}}</action>
<action>Read ALL lines from beginning to end - do not skip any content</action>
<action>Parse the development_status section completely</action>
<action>Find the FIRST story (by reading in order from top to bottom) where:
- Key matches pattern: number-number-name (e.g., "1-2-user-auth")
- NOT an epic key (epic-X) or retrospective (epic-X-retrospective)
- Status value equals "backlog"
</action>
<check if="no backlog story found">
<output>📋 No backlog stories found in sprint-status.yaml
All stories are either already created, in progress, or done.
**Options:**
1. Run sprint-planning to refresh story tracking
2. Load PM agent and run correct-course to add more stories
3. Check if current sprint is complete and run retrospective
</output>
<action>HALT</action>
</check>
<action>Extract from found story key (e.g., "1-2-user-authentication"):
- epic_num: first number before dash (e.g., "1")
- story_num: second number after first dash (e.g., "2")
- story_title: remainder after second dash (e.g., "user-authentication")
</action>
<action>Set {{story_id}} = "{{epic_num}}.{{story_num}}"</action>
<action>Store story_key for later use (e.g., "1-2-user-authentication")</action>
<!-- Mark epic as in-progress if this is first story -->
<action>Check if this is the first story in epic {{epic_num}} by looking for {{epic_num}}-1-* pattern</action>
<check if="this is first story in epic {{epic_num}}">
<action>Load {{sprint_status}} and check epic-{{epic_num}} status</action>
<action>If epic status is "backlog" → update to "in-progress"</action>
<action>If epic status is "contexted" (legacy status) → update to "in-progress" (backward compatibility)</action>
<action>If epic status is "in-progress" → no change needed</action>
<check if="epic status is 'done'">
<output>🚫 ERROR: Cannot create story in completed epic</output>
<output>Epic {{epic_num}} is marked as 'done'. All stories are complete.</output>
<output>If you need to add more work, either:</output>
<output>1. Manually change epic status back to 'in-progress' in sprint-status.yaml</output>
<output>2. Create a new epic for additional work</output>
<action>HALT - Cannot proceed</action>
</check>
<check if="epic status is not one of: backlog, contexted, in-progress, done">
<output>🚫 ERROR: Invalid epic status '{{epic_status}}'</output>
<output>Epic {{epic_num}} has invalid status. Expected: backlog, in-progress, or done</output>
<output>Please fix sprint-status.yaml manually or run sprint-planning to regenerate</output>
<action>HALT - Cannot proceed</action>
</check>
<output>📊 Epic {{epic_num}} status updated to in-progress</output>
</check>
<action>GOTO step 2a</action>
</check>
<action>Load the FULL file: {{sprint_status}}</action>
<action>Read ALL lines from beginning to end - do not skip any content</action>
<action>Parse the development_status section completely</action>
<action>Find the FIRST story (by reading in order from top to bottom) where:
- Key matches pattern: number-number-name (e.g., "1-2-user-auth")
- NOT an epic key (epic-X) or retrospective (epic-X-retrospective)
- Status value equals "backlog"
</action>
<check if="no backlog story found">
<output>No backlog stories found in sprint-status.yaml
All stories are either already created, in progress, or done.
**Options:**
1. Run sprint-planning to refresh story tracking
2. Load PM agent and run correct-course to add more stories
3. Check if current sprint is complete and run retrospective
</output>
<action>HALT</action>
</check>
<action>Extract from found story key (e.g., "1-2-user-authentication"):
- epic_num: first number before dash (e.g., "1")
- story_num: second number after first dash (e.g., "2")
- story_title: remainder after second dash (e.g., "user-authentication")
</action>
<action>Set {{story_id}} = "{{epic_num}}.{{story_num}}"</action>
<action>Store story_key for later use (e.g., "1-2-user-authentication")</action>
<!-- Mark epic as in-progress if this is first story -->
<action>Check if this is the first story in epic {{epic_num}} by looking for {{epic_num}}-1-* pattern</action>
<check if="this is first story in epic {{epic_num}}">
<action>Load {{sprint_status}} and check epic-{{epic_num}} status</action>
<action>If epic status is "backlog" → update to "in-progress"</action>
<action>If epic status is "contexted" (legacy status) → update to "in-progress" (backward compatibility)</action>
<action>If epic status is "in-progress" → no change needed</action>
<check if="epic status is 'done'">
<output>ERROR: Cannot create story in completed epic</output>
<output>Epic {{epic_num}} is marked as 'done'. All stories are complete.</output>
<output>If you need to add more work, either:</output>
<output>1. Manually change epic status back to 'in-progress' in sprint-status.yaml</output>
<output>2. Create a new epic for additional work</output>
<action>HALT - Cannot proceed</action>
</check>
<check if="epic status is not one of: backlog, contexted, in-progress, done">
<output>ERROR: Invalid epic status '{{epic_status}}'</output>
<output>Epic {{epic_num}} has invalid status. Expected: backlog, in-progress, or done</output>
<output>Please fix sprint-status.yaml manually or run sprint-planning to regenerate</output>
<action>HALT - Cannot proceed</action>
</check>
<output>Epic {{epic_num}} status updated to in-progress</output>
</check>
<action>GOTO step 2a</action>
</step>
<step n="2" goal="Load and analyze core artifacts">
<critical>🔬 EXHAUSTIVE ARTIFACT ANALYSIS - This is where you prevent future developer mistakes!</critical>
<!-- Load all available content through discovery protocol -->
<action>Read fully and follow `./discover-inputs.md` to load all input files</action>
<note>Available content: {epics_content}, {prd_content}, {architecture_content}, {ux_content}, plus the project-context facts loaded during activation via `persistent_facts`.</note>
<!-- Analyze epics file for story foundation -->
<action>From {epics_content}, extract Epic {{epic_num}} complete context:</action> **EPIC ANALYSIS:** - Epic
objectives and business value - ALL stories in this epic for cross-story context - Our specific story's requirements, user story
statement, acceptance criteria - Technical requirements and constraints - Dependencies on other stories/epics - Source hints pointing to
original documents <!-- Extract specific story requirements -->
<action>Extract our story ({{epic_num}}-{{story_num}}) details:</action> **STORY FOUNDATION:** - User story statement
(As a, I want, so that) - Detailed acceptance criteria (already BDD formatted) - Technical requirements specific to this story -
Business context and value - Success criteria <!-- Previous story analysis for context continuity -->
<check if="story_num > 1">
<action>Find {{previous_story_num}}: scan {implementation_artifacts} for the story file in epic {{epic_num}} with the highest story number less than {{story_num}}</action>
<action>Load previous story file: {implementation_artifacts}/{{epic_num}}-{{previous_story_num}}-*.md</action> **PREVIOUS STORY INTELLIGENCE:** -
Dev notes and learnings from previous story - Review feedback and corrections needed - Files that were created/modified and their
patterns - Testing approaches that worked/didn't work - Problems encountered and solutions found - Code patterns established <action>Extract
all learnings that could impact current story implementation</action>
</check>
<!-- Git intelligence for previous work patterns -->
<check
if="previous story exists AND git repository detected">
<action>Get last 5 commit titles to understand recent work patterns</action>
<action>Analyze 1-5 most recent commits for relevance to current story:
- Files created/modified
- Code patterns and conventions used
- Library dependencies added/changed
- Architecture decisions implemented
- Testing approaches used
</action>
<action>Extract actionable insights for current story implementation</action>
</check>
</step>
<step n="3" goal="Architecture analysis for developer guardrails">
<critical>🏗️ ARCHITECTURE INTELLIGENCE - Extract everything the developer MUST follow!</critical> **ARCHITECTURE DOCUMENT ANALYSIS:** <action>Systematically
analyze architecture content for story-relevant requirements:</action>
<!-- Load architecture - single file or sharded -->
<check if="architecture file is single file">
<action>Load complete {architecture_content}</action>
</check>
<check if="architecture is sharded to folder">
<action>Load architecture index and scan all architecture files</action>
</check> **CRITICAL ARCHITECTURE EXTRACTION:** <action>For
each architecture section, determine if relevant to this story:</action> - **Technical Stack:** Languages, frameworks, libraries with
versions - **Code Structure:** Folder organization, naming conventions, file patterns - **API Patterns:** Service structure, endpoint
patterns, data contracts - **Database Schemas:** Tables, relationships, constraints relevant to story - **Security Requirements:**
Authentication patterns, authorization rules - **Performance Requirements:** Caching strategies, optimization patterns - **Testing
Standards:** Testing frameworks, coverage expectations, test patterns - **Deployment Patterns:** Environment configurations, build
processes - **Integration Patterns:** External service integrations, data flows <action>Extract any story-specific requirements that the
developer MUST follow</action>
<action>Identify any architectural decisions that override previous patterns</action>
</step>
<step n="4" goal="Web research for latest technical specifics">
<critical>🌐 ENSURE LATEST TECH KNOWLEDGE - Prevent outdated implementations!</critical> **WEB INTELLIGENCE:** <action>Identify specific
technical areas that require latest version knowledge:</action>
<!-- Check for libraries/frameworks mentioned in architecture -->
<action>From architecture analysis, identify specific libraries, APIs, or
frameworks</action>
<action>For each critical technology, research latest stable version and key changes:
- Latest API documentation and breaking changes
- Security vulnerabilities or updates
- Performance improvements or deprecations
- Best practices for current version
</action>
**EXTERNAL CONTEXT INCLUSION:** <action>Include in story any critical latest information the developer needs:
- Specific library versions and why chosen
- API endpoints with parameters and authentication
- Recent security patches or considerations
- Performance optimization techniques
- Migration considerations if upgrading
</action>
</step>
<step n="5" goal="Create comprehensive story file">
<critical>📝 CREATE ULTIMATE STORY FILE - The developer's master implementation guide!</critical>
<action>Initialize from template.md:
{default_output_file}</action>
<template-output file="{default_output_file}">story_header</template-output>
<!-- Story foundation from epics analysis -->
<template-output
file="{default_output_file}">story_requirements</template-output>
<!-- Developer context section - MOST IMPORTANT PART -->
<template-output file="{default_output_file}">
developer_context_section</template-output> **DEV AGENT GUARDRAILS:** <template-output file="{default_output_file}">
technical_requirements</template-output>
<template-output file="{default_output_file}">architecture_compliance</template-output>
<template-output
file="{default_output_file}">library_framework_requirements</template-output>
<template-output file="{default_output_file}">
file_structure_requirements</template-output>
<template-output file="{default_output_file}">testing_requirements</template-output>
<!-- Previous story intelligence -->
<check
if="previous story learnings available">
<template-output file="{default_output_file}">previous_story_intelligence</template-output>
</check>
<!-- Git intelligence -->
<check
if="git analysis completed">
<template-output file="{default_output_file}">git_intelligence_summary</template-output>
</check>
<!-- Latest technical specifics -->
<check if="web research completed">
<template-output file="{default_output_file}">latest_tech_information</template-output>
</check>
<!-- Project context reference -->
<template-output
file="{default_output_file}">project_context_reference</template-output>
<!-- Final status update -->
<template-output file="{default_output_file}">
story_completion_status</template-output>
<!-- CRITICAL: Set status to ready-for-dev -->
<action>Set story Status to: "ready-for-dev"</action>
<action>Add completion note: "Ultimate
context engine analysis completed - comprehensive developer guide created"</action>
</step>
<step n="6" goal="Update sprint status and finalize">
<action>Validate the newly created story file {default_output_file} against `./checklist.md` and apply any required fixes before finalizing</action>
<action>Save story document unconditionally</action>
<!-- Update sprint status -->
<check if="sprint status file exists">
<action>Update {{sprint_status}}</action>
<action>Load the FULL file and read all development_status entries</action>
<action>Find development_status key matching {{story_key}}</action>
<action>Verify current status is "backlog" (expected previous state)</action>
<action>Update development_status[{{story_key}}] = "ready-for-dev"</action>
<action>Update last_updated field to current date</action>
<action>Save file, preserving ALL comments and structure including STATUS DEFINITIONS</action>
</check>
<action>Report completion</action>
<output>**🎯 ULTIMATE BMad Method STORY CONTEXT CREATED, {user_name}!**
**Story Details:**
- Story ID: {{story_id}}
- Story Key: {{story_key}}
- File: {{story_file}}
- Status: ready-for-dev
**Next Steps:**
1. Review the comprehensive story in {{story_file}}
2. Run dev agents `dev-story` for optimized implementation
3. Run `code-review` when complete (auto-marks done)
4. Optional: If Test Architect module installed, run `/bmad:tea:automate` after `dev-story` to generate guardrail tests
**The developer now has everything needed for flawless implementation!**
</output>
<action>Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
</step>
</workflow>

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-create-story. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All stories must include testable acceptance criteria."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 6 (Update sprint status and finalize),
# after the story file is saved and sprint-status.yaml is updated. Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -1,380 +0,0 @@
# Create Story Workflow
**Goal:** Create a comprehensive story file that gives the dev agent everything needed for flawless implementation.
**Your Role:** Story context engine that prevents LLM developer mistakes, omissions, or disasters.
- Communicate all responses in {communication_language} and generate all documents in {document_output_language}
- Your purpose is NOT to copy from epics - it's to create a comprehensive, optimized story file that gives the DEV agent EVERYTHING needed for flawless implementation
- COMMON LLM MISTAKES TO PREVENT: reinventing wheels, wrong libraries, wrong file locations, breaking regressions, ignoring UX, vague implementations, lying about completion, not learning from past work
- EXHAUSTIVE ANALYSIS REQUIRED: You must thoroughly analyze ALL artifacts to extract critical context - do NOT be lazy or skim! This is the most important function in the entire development process!
- UTILIZE SUBPROCESSES AND SUBAGENTS: Use research subagents, subprocesses or parallel processing if available to thoroughly analyze different artifacts simultaneously and thoroughly
- SAVE QUESTIONS: If you think of questions or clarifications during analysis, save them for the end after the complete story is written
- ZERO USER INTERVENTION: Process should be fully automated except for initial epic/story selection or missing documents
---
## INITIALIZATION
### Configuration Loading
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `user_name`
- `communication_language`, `document_output_language`
- `user_skill_level`
- `planning_artifacts`, `implementation_artifacts`
- `date` as system-generated current datetime
### Paths
- `sprint_status` = `{implementation_artifacts}/sprint-status.yaml`
- `epics_file` = `{planning_artifacts}/epics.md`
- `prd_file` = `{planning_artifacts}/prd.md`
- `architecture_file` = `{planning_artifacts}/architecture.md`
- `ux_file` = `{planning_artifacts}/*ux*.md`
- `story_title` = "" (will be elicited if not derivable)
- `project_context` = `**/project-context.md` (load if exists)
- `default_output_file` = `{implementation_artifacts}/{{story_key}}.md`
### Input Files
| Input | Description | Path Pattern(s) | Load Strategy |
|-------|-------------|------------------|---------------|
| prd | PRD (fallback - epics file should have most content) | whole: `{planning_artifacts}/*prd*.md`, sharded: `{planning_artifacts}/*prd*/*.md` | SELECTIVE_LOAD |
| architecture | Architecture (fallback - epics file should have relevant sections) | whole: `{planning_artifacts}/*architecture*.md`, sharded: `{planning_artifacts}/*architecture*/*.md` | SELECTIVE_LOAD |
| ux | UX design (fallback - epics file should have relevant sections) | whole: `{planning_artifacts}/*ux*.md`, sharded: `{planning_artifacts}/*ux*/*.md` | SELECTIVE_LOAD |
| epics | Enhanced epics+stories file with BDD and source hints | whole: `{planning_artifacts}/*epic*.md`, sharded: `{planning_artifacts}/*epic*/*.md` | SELECTIVE_LOAD |
---
## EXECUTION
<workflow>
<step n="1" goal="Determine target story">
<check if="{{story_path}} is provided by user or user provided the epic and story number such as 2-4 or 1.6 or epic 1 story 5">
<action>Parse user-provided story path: extract epic_num, story_num, story_title from format like "1-2-user-auth"</action>
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>
<action>GOTO step 2a</action>
</check>
<action>Check if {{sprint_status}} file exists for auto discover</action>
<check if="sprint status file does NOT exist">
<output>🚫 No sprint status file found and no story specified</output>
<output>
**Required Options:**
1. Run `sprint-planning` to initialize sprint tracking (recommended)
2. Provide specific epic-story number to create (e.g., "1-2-user-auth")
3. Provide path to story documents if sprint status doesn't exist yet
</output>
<ask>Choose option [1], provide epic-story number, path to story docs, or [q] to quit:</ask>
<check if="user chooses 'q'">
<action>HALT - No work needed</action>
</check>
<check if="user chooses '1'">
<output>Run sprint-planning workflow first to create sprint-status.yaml</output>
<action>HALT - User needs to run sprint-planning</action>
</check>
<check if="user provides epic-story number">
<action>Parse user input: extract epic_num, story_num, story_title</action>
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>
<action>GOTO step 2a</action>
</check>
<check if="user provides story docs path">
<action>Use user-provided path for story documents</action>
<action>GOTO step 2a</action>
</check>
</check>
<!-- Auto-discover from sprint status only if no user input -->
<check if="no user input provided">
<critical>MUST read COMPLETE {sprint_status} file from start to end to preserve order</critical>
<action>Load the FULL file: {{sprint_status}}</action>
<action>Read ALL lines from beginning to end - do not skip any content</action>
<action>Parse the development_status section completely</action>
<action>Find the FIRST story (by reading in order from top to bottom) where:
- Key matches pattern: number-number-name (e.g., "1-2-user-auth")
- NOT an epic key (epic-X) or retrospective (epic-X-retrospective)
- Status value equals "backlog"
</action>
<check if="no backlog story found">
<output>📋 No backlog stories found in sprint-status.yaml
All stories are either already created, in progress, or done.
**Options:**
1. Run sprint-planning to refresh story tracking
2. Load PM agent and run correct-course to add more stories
3. Check if current sprint is complete and run retrospective
</output>
<action>HALT</action>
</check>
<action>Extract from found story key (e.g., "1-2-user-authentication"):
- epic_num: first number before dash (e.g., "1")
- story_num: second number after first dash (e.g., "2")
- story_title: remainder after second dash (e.g., "user-authentication")
</action>
<action>Set {{story_id}} = "{{epic_num}}.{{story_num}}"</action>
<action>Store story_key for later use (e.g., "1-2-user-authentication")</action>
<!-- Mark epic as in-progress if this is first story -->
<action>Check if this is the first story in epic {{epic_num}} by looking for {{epic_num}}-1-* pattern</action>
<check if="this is first story in epic {{epic_num}}">
<action>Load {{sprint_status}} and check epic-{{epic_num}} status</action>
<action>If epic status is "backlog" → update to "in-progress"</action>
<action>If epic status is "contexted" (legacy status) → update to "in-progress" (backward compatibility)</action>
<action>If epic status is "in-progress" → no change needed</action>
<check if="epic status is 'done'">
<output>🚫 ERROR: Cannot create story in completed epic</output>
<output>Epic {{epic_num}} is marked as 'done'. All stories are complete.</output>
<output>If you need to add more work, either:</output>
<output>1. Manually change epic status back to 'in-progress' in sprint-status.yaml</output>
<output>2. Create a new epic for additional work</output>
<action>HALT - Cannot proceed</action>
</check>
<check if="epic status is not one of: backlog, contexted, in-progress, done">
<output>🚫 ERROR: Invalid epic status '{{epic_status}}'</output>
<output>Epic {{epic_num}} has invalid status. Expected: backlog, in-progress, or done</output>
<output>Please fix sprint-status.yaml manually or run sprint-planning to regenerate</output>
<action>HALT - Cannot proceed</action>
</check>
<output>📊 Epic {{epic_num}} status updated to in-progress</output>
</check>
<action>GOTO step 2a</action>
</check>
<action>Load the FULL file: {{sprint_status}}</action>
<action>Read ALL lines from beginning to end - do not skip any content</action>
<action>Parse the development_status section completely</action>
<action>Find the FIRST story (by reading in order from top to bottom) where:
- Key matches pattern: number-number-name (e.g., "1-2-user-auth")
- NOT an epic key (epic-X) or retrospective (epic-X-retrospective)
- Status value equals "backlog"
</action>
<check if="no backlog story found">
<output>No backlog stories found in sprint-status.yaml
All stories are either already created, in progress, or done.
**Options:**
1. Run sprint-planning to refresh story tracking
2. Load PM agent and run correct-course to add more stories
3. Check if current sprint is complete and run retrospective
</output>
<action>HALT</action>
</check>
<action>Extract from found story key (e.g., "1-2-user-authentication"):
- epic_num: first number before dash (e.g., "1")
- story_num: second number after first dash (e.g., "2")
- story_title: remainder after second dash (e.g., "user-authentication")
</action>
<action>Set {{story_id}} = "{{epic_num}}.{{story_num}}"</action>
<action>Store story_key for later use (e.g., "1-2-user-authentication")</action>
<!-- Mark epic as in-progress if this is first story -->
<action>Check if this is the first story in epic {{epic_num}} by looking for {{epic_num}}-1-* pattern</action>
<check if="this is first story in epic {{epic_num}}">
<action>Load {{sprint_status}} and check epic-{{epic_num}} status</action>
<action>If epic status is "backlog" → update to "in-progress"</action>
<action>If epic status is "contexted" (legacy status) → update to "in-progress" (backward compatibility)</action>
<action>If epic status is "in-progress" → no change needed</action>
<check if="epic status is 'done'">
<output>ERROR: Cannot create story in completed epic</output>
<output>Epic {{epic_num}} is marked as 'done'. All stories are complete.</output>
<output>If you need to add more work, either:</output>
<output>1. Manually change epic status back to 'in-progress' in sprint-status.yaml</output>
<output>2. Create a new epic for additional work</output>
<action>HALT - Cannot proceed</action>
</check>
<check if="epic status is not one of: backlog, contexted, in-progress, done">
<output>ERROR: Invalid epic status '{{epic_status}}'</output>
<output>Epic {{epic_num}} has invalid status. Expected: backlog, in-progress, or done</output>
<output>Please fix sprint-status.yaml manually or run sprint-planning to regenerate</output>
<action>HALT - Cannot proceed</action>
</check>
<output>Epic {{epic_num}} status updated to in-progress</output>
</check>
<action>GOTO step 2a</action>
</step>
<step n="2" goal="Load and analyze core artifacts">
<critical>🔬 EXHAUSTIVE ARTIFACT ANALYSIS - This is where you prevent future developer mistakes!</critical>
<!-- Load all available content through discovery protocol -->
<action>Read fully and follow `./discover-inputs.md` to load all input files</action>
<note>Available content: {epics_content}, {prd_content}, {architecture_content}, {ux_content},
{project_context}</note>
<!-- Analyze epics file for story foundation -->
<action>From {epics_content}, extract Epic {{epic_num}} complete context:</action> **EPIC ANALYSIS:** - Epic
objectives and business value - ALL stories in this epic for cross-story context - Our specific story's requirements, user story
statement, acceptance criteria - Technical requirements and constraints - Dependencies on other stories/epics - Source hints pointing to
original documents <!-- Extract specific story requirements -->
<action>Extract our story ({{epic_num}}-{{story_num}}) details:</action> **STORY FOUNDATION:** - User story statement
(As a, I want, so that) - Detailed acceptance criteria (already BDD formatted) - Technical requirements specific to this story -
Business context and value - Success criteria <!-- Previous story analysis for context continuity -->
<check if="story_num > 1">
<action>Find {{previous_story_num}}: scan {implementation_artifacts} for the story file in epic {{epic_num}} with the highest story number less than {{story_num}}</action>
<action>Load previous story file: {implementation_artifacts}/{{epic_num}}-{{previous_story_num}}-*.md</action> **PREVIOUS STORY INTELLIGENCE:** -
Dev notes and learnings from previous story - Review feedback and corrections needed - Files that were created/modified and their
patterns - Testing approaches that worked/didn't work - Problems encountered and solutions found - Code patterns established <action>Extract
all learnings that could impact current story implementation</action>
</check>
<!-- Git intelligence for previous work patterns -->
<check
if="previous story exists AND git repository detected">
<action>Get last 5 commit titles to understand recent work patterns</action>
<action>Analyze 1-5 most recent commits for relevance to current story:
- Files created/modified
- Code patterns and conventions used
- Library dependencies added/changed
- Architecture decisions implemented
- Testing approaches used
</action>
<action>Extract actionable insights for current story implementation</action>
</check>
</step>
<step n="3" goal="Architecture analysis for developer guardrails">
<critical>🏗️ ARCHITECTURE INTELLIGENCE - Extract everything the developer MUST follow!</critical> **ARCHITECTURE DOCUMENT ANALYSIS:** <action>Systematically
analyze architecture content for story-relevant requirements:</action>
<!-- Load architecture - single file or sharded -->
<check if="architecture file is single file">
<action>Load complete {architecture_content}</action>
</check>
<check if="architecture is sharded to folder">
<action>Load architecture index and scan all architecture files</action>
</check> **CRITICAL ARCHITECTURE EXTRACTION:** <action>For
each architecture section, determine if relevant to this story:</action> - **Technical Stack:** Languages, frameworks, libraries with
versions - **Code Structure:** Folder organization, naming conventions, file patterns - **API Patterns:** Service structure, endpoint
patterns, data contracts - **Database Schemas:** Tables, relationships, constraints relevant to story - **Security Requirements:**
Authentication patterns, authorization rules - **Performance Requirements:** Caching strategies, optimization patterns - **Testing
Standards:** Testing frameworks, coverage expectations, test patterns - **Deployment Patterns:** Environment configurations, build
processes - **Integration Patterns:** External service integrations, data flows <action>Extract any story-specific requirements that the
developer MUST follow</action>
<action>Identify any architectural decisions that override previous patterns</action>
</step>
<step n="4" goal="Web research for latest technical specifics">
<critical>🌐 ENSURE LATEST TECH KNOWLEDGE - Prevent outdated implementations!</critical> **WEB INTELLIGENCE:** <action>Identify specific
technical areas that require latest version knowledge:</action>
<!-- Check for libraries/frameworks mentioned in architecture -->
<action>From architecture analysis, identify specific libraries, APIs, or
frameworks</action>
<action>For each critical technology, research latest stable version and key changes:
- Latest API documentation and breaking changes
- Security vulnerabilities or updates
- Performance improvements or deprecations
- Best practices for current version
</action>
**EXTERNAL CONTEXT INCLUSION:** <action>Include in story any critical latest information the developer needs:
- Specific library versions and why chosen
- API endpoints with parameters and authentication
- Recent security patches or considerations
- Performance optimization techniques
- Migration considerations if upgrading
</action>
</step>
<step n="5" goal="Create comprehensive story file">
<critical>📝 CREATE ULTIMATE STORY FILE - The developer's master implementation guide!</critical>
<action>Initialize from template.md:
{default_output_file}</action>
<template-output file="{default_output_file}">story_header</template-output>
<!-- Story foundation from epics analysis -->
<template-output
file="{default_output_file}">story_requirements</template-output>
<!-- Developer context section - MOST IMPORTANT PART -->
<template-output file="{default_output_file}">
developer_context_section</template-output> **DEV AGENT GUARDRAILS:** <template-output file="{default_output_file}">
technical_requirements</template-output>
<template-output file="{default_output_file}">architecture_compliance</template-output>
<template-output
file="{default_output_file}">library_framework_requirements</template-output>
<template-output file="{default_output_file}">
file_structure_requirements</template-output>
<template-output file="{default_output_file}">testing_requirements</template-output>
<!-- Previous story intelligence -->
<check
if="previous story learnings available">
<template-output file="{default_output_file}">previous_story_intelligence</template-output>
</check>
<!-- Git intelligence -->
<check
if="git analysis completed">
<template-output file="{default_output_file}">git_intelligence_summary</template-output>
</check>
<!-- Latest technical specifics -->
<check if="web research completed">
<template-output file="{default_output_file}">latest_tech_information</template-output>
</check>
<!-- Project context reference -->
<template-output
file="{default_output_file}">project_context_reference</template-output>
<!-- Final status update -->
<template-output file="{default_output_file}">
story_completion_status</template-output>
<!-- CRITICAL: Set status to ready-for-dev -->
<action>Set story Status to: "ready-for-dev"</action>
<action>Add completion note: "Ultimate
context engine analysis completed - comprehensive developer guide created"</action>
</step>
<step n="6" goal="Update sprint status and finalize">
<action>Validate the newly created story file {default_output_file} against `./checklist.md` and apply any required fixes before finalizing</action>
<action>Save story document unconditionally</action>
<!-- Update sprint status -->
<check if="sprint status file exists">
<action>Update {{sprint_status}}</action>
<action>Load the FULL file and read all development_status entries</action>
<action>Find development_status key matching {{story_key}}</action>
<action>Verify current status is "backlog" (expected previous state)</action>
<action>Update development_status[{{story_key}}] = "ready-for-dev"</action>
<action>Update last_updated field to current date</action>
<action>Save file, preserving ALL comments and structure including STATUS DEFINITIONS</action>
</check>
<action>Report completion</action>
<output>**🎯 ULTIMATE BMad Method STORY CONTEXT CREATED, {user_name}!**
**Story Details:**
- Story ID: {{story_id}}
- Story Key: {{story_key}}
- File: {{story_file}}
- Status: ready-for-dev
**Next Steps:**
1. Review the comprehensive story in {{story_file}}
2. Run dev agents `dev-story` for optimized implementation
3. Run `code-review` when complete (auto-marks done)
4. Optional: If Test Architect module installed, run `/bmad:tea:automate` after `dev-story` to generate guardrail tests
**The developer now has everything needed for flawless implementation!**
</output>
</step>
</workflow>

View File

@ -5,10 +5,10 @@
**Your Role:** Developer implementing the story.
- Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}
- Generate all documents in {document_output_language}
- Only modify the story file in these areas: Tasks/Subtasks checkboxes, Dev Agent Record (Debug Log, Completion Notes), File List, Change Log, and Status
- Only modify the story file in these areas: Tasks/Subtasks checkboxes (including Review Follow-ups (AI)), Dev Agent Record (Debug Log, Completion Notes), File List, Change Log, and Status
- Execute ALL steps in exact order; do NOT skip steps
- Absolutely DO NOT stop because of "milestones", "significant progress", or "session boundaries". Continue in a single execution until the story is COMPLETE (all ACs satisfied and all tasks/subtasks checked) UNLESS a HALT condition is triggered or the USER gives other instruction.
- Do NOT schedule a "next session" or request review pauses unless a HALT condition applies. Only Step 6 decides completion.
- Do NOT schedule a "next session" or request review pauses unless a HALT condition applies. Only Step 8 decides completion.
- User skill level ({user_skill_level}) affects conversation style ONLY, not code updates.
---
@ -47,7 +47,7 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
<critical>Absolutely DO NOT stop because of "milestones", "significant progress", or "session boundaries". Continue in a single execution
until the story is COMPLETE (all ACs satisfied and all tasks/subtasks checked) UNLESS a HALT condition is triggered or the USER gives
other instruction.</critical>
<critical>Do NOT schedule a "next session" or request review pauses unless a HALT condition applies. Only Step 6 decides completion.</critical>
<critical>Do NOT schedule a "next session" or request review pauses unless a HALT condition applies. Only Step 8 decides completion.</critical>
<critical>User skill level ({user_skill_level}) affects conversation style ONLY, not code updates.</critical>
<step n="1" goal="Find next ready story and load it" tag="sprint-status">

View File

@ -3,4 +3,174 @@ name: bmad-qa-generate-e2e-tests
description: 'Generate end to end automated tests for existing features. Use when the user says "create qa automated tests for [feature]"'
---
Follow the instructions in ./workflow.md.
# QA Generate E2E Tests Workflow
**Goal:** Generate automated API and E2E tests for implemented code.
**Your Role:** You are a QA automation engineer. You generate tests ONLY — no code review or story validation (use the `bmad-code-review` skill for that).
## Conventions
- Bare paths (e.g. `checklist.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
### Step 2: Execute Prepend Steps
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
### Step 3: Load Persistent Facts
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
### Step 4: Load Config
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `user_name`
- `communication_language`, `document_output_language`
- `implementation_artifacts`
- `date` as system-generated current datetime
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
### Step 5: Greet the User
Greet `{user_name}`, speaking in `{communication_language}`.
### Step 6: Execute Append Steps
Execute each entry in `{workflow.activation_steps_append}` in order.
Activation is complete. Begin the workflow below.
## Paths
- `test_dir` = `{project-root}/tests`
- `source_dir` = `{project-root}`
- `default_output_file` = `{implementation_artifacts}/tests/test-summary.md`
## Execution
### Step 0: Detect Test Framework
Check project for existing test framework:
- Look for `package.json` dependencies (playwright, jest, vitest, cypress, etc.)
- Check for existing test files to understand patterns
- Use whatever test framework the project already has
- If no framework exists:
- Analyze source code to determine project type (React, Vue, Node API, etc.)
- Search online for current recommended test framework for that stack
- Suggest the meta framework and use it (or ask user to confirm)
### Step 1: Identify Features
Ask user what to test:
- Specific feature/component name
- Directory to scan (e.g., `src/components/`)
- Or auto-discover features in the codebase
### Step 2: Generate API Tests (if applicable)
For API endpoints/services, generate tests that:
- Test status codes (200, 400, 404, 500)
- Validate response structure
- Cover happy path + 1-2 error cases
- Use project's existing test framework patterns
### Step 3: Generate E2E Tests (if UI exists)
For UI features, generate tests that:
- Test user workflows end-to-end
- Use semantic locators (roles, labels, text)
- Focus on user interactions (clicks, form fills, navigation)
- Assert visible outcomes
- Keep tests linear and simple
- Follow project's existing test patterns
### Step 4: Run Tests
Execute tests to verify they pass (use project's test command).
If failures occur, fix them immediately.
### Step 5: Create Summary
Output markdown summary:
```markdown
# Test Automation Summary
## Generated Tests
### API Tests
- [x] tests/api/endpoint.spec.ts - Endpoint validation
### E2E Tests
- [x] tests/e2e/feature.spec.ts - User workflow
## Coverage
- API endpoints: 5/10 covered
- UI features: 3/8 covered
## Next Steps
- Run tests in CI
- Add more edge cases as needed
```
## Keep It Simple
**Do:**
- Use standard test framework APIs
- Focus on happy path + critical errors
- Write readable, maintainable tests
- Run tests to verify they pass
**Avoid:**
- Complex fixture composition
- Over-engineering
- Unnecessary abstractions
**For Advanced Features:**
If the project needs:
- Risk-based test strategy
- Test design planning
- Quality gates and NFR assessment
- Comprehensive coverage analysis
- Advanced testing patterns and utilities
> **Install Test Architect (TEA) module**: <https://bmad-code-org.github.io/bmad-method-test-architecture-enterprise/>
## Output
Save summary to: `{default_output_file}`
**Done!** Tests generated and verified. Validate against `./checklist.md`.
## On Complete
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-qa-generate-e2e-tests. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All tests must follow the project's existing test framework patterns."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 5 (Create Summary),
# after all tests pass and the summary document is saved. Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

View File

@ -1,136 +0,0 @@
# QA Generate E2E Tests Workflow
**Goal:** Generate automated API and E2E tests for implemented code.
**Your Role:** You are a QA automation engineer. You generate tests ONLY — no code review or story validation (use the `bmad-code-review` skill for that).
---
## INITIALIZATION
### Configuration Loading
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `user_name`
- `communication_language`, `document_output_language`
- `implementation_artifacts`
- `date` as system-generated current datetime
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
### Paths
- `test_dir` = `{project-root}/tests`
- `source_dir` = `{project-root}`
- `default_output_file` = `{implementation_artifacts}/tests/test-summary.md`
### Context
- `project_context` = `**/project-context.md` (load if exists)
---
## EXECUTION
### Step 0: Detect Test Framework
Check project for existing test framework:
- Look for `package.json` dependencies (playwright, jest, vitest, cypress, etc.)
- Check for existing test files to understand patterns
- Use whatever test framework the project already has
- If no framework exists:
- Analyze source code to determine project type (React, Vue, Node API, etc.)
- Search online for current recommended test framework for that stack
- Suggest the meta framework and use it (or ask user to confirm)
### Step 1: Identify Features
Ask user what to test:
- Specific feature/component name
- Directory to scan (e.g., `src/components/`)
- Or auto-discover features in the codebase
### Step 2: Generate API Tests (if applicable)
For API endpoints/services, generate tests that:
- Test status codes (200, 400, 404, 500)
- Validate response structure
- Cover happy path + 1-2 error cases
- Use project's existing test framework patterns
### Step 3: Generate E2E Tests (if UI exists)
For UI features, generate tests that:
- Test user workflows end-to-end
- Use semantic locators (roles, labels, text)
- Focus on user interactions (clicks, form fills, navigation)
- Assert visible outcomes
- Keep tests linear and simple
- Follow project's existing test patterns
### Step 4: Run Tests
Execute tests to verify they pass (use project's test command).
If failures occur, fix them immediately.
### Step 5: Create Summary
Output markdown summary:
```markdown
# Test Automation Summary
## Generated Tests
### API Tests
- [x] tests/api/endpoint.spec.ts - Endpoint validation
### E2E Tests
- [x] tests/e2e/feature.spec.ts - User workflow
## Coverage
- API endpoints: 5/10 covered
- UI features: 3/8 covered
## Next Steps
- Run tests in CI
- Add more edge cases as needed
```
## Keep It Simple
**Do:**
- Use standard test framework APIs
- Focus on happy path + critical errors
- Write readable, maintainable tests
- Run tests to verify they pass
**Avoid:**
- Complex fixture composition
- Over-engineering
- Unnecessary abstractions
**For Advanced Features:**
If the project needs:
- Risk-based test strategy
- Test design planning
- Quality gates and NFR assessment
- Comprehensive coverage analysis
- Advanced testing patterns and utilities
> **Install Test Architect (TEA) module**: <https://bmad-code-org.github.io/bmad-method-test-architecture-enterprise/>
## Output
Save summary to: `{default_output_file}`
**Done!** Tests generated and verified. Validate against `./checklist.md`.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-retrospective. Mirrors the
# agent customization shape under the [workflow] namespace.
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
# Steps to run before the standard activation (config load, greet).
# Overrides append. Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the workflow begins.
# Overrides append. Use for context-heavy setup that should happen
# once the user has been acknowledged.
activation_steps_append = []
# Persistent facts the workflow keeps in mind for the whole run
# (standards, compliance constraints, stylistic guardrails).
# Distinct from the runtime memory sidecar — these are static context
# loaded on activation. Overrides append.
#
# Each entry is either:
# - a literal sentence, e.g. "All retrospectives must produce SMART action items with named owners."
# - a file reference prefixed with `file:`, e.g. "file:{project-root}/docs/standards.md"
# (glob patterns are supported; the file's contents are loaded and treated as facts).
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Scalar: executed when the workflow reaches Step 12 (Final Summary and Handoff),
# after the retrospective document is saved and sprint-status is updated. Override wins.
# Leave empty for no custom post-completion behavior.
on_complete = ""

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,111 @@
---
name: bmad-customize
description: Authors and updates customization overrides for installed BMad skills. Use when the user says 'customize bmad', 'override a skill', 'change agent behavior', or 'customize a workflow'.
---
# BMad Customize
Translate the user's intent into a correctly-placed TOML override file under `{project-root}/_bmad/custom/` for a customizable agent or workflow skill. Discover, route, author, write, verify.
Scope v1: per-skill `[agent]` overrides (`bmad-agent-<role>.toml` / `.user.toml`) and per-skill `[workflow]` overrides (`bmad-<workflow>.toml` / `.user.toml`). Central config (`{project-root}/_bmad/custom/config.toml`) is out of scope — point users at the [How to Customize BMad guide](https://docs.bmad-method.org/how-to/customize-bmad/).
When the target's `customize.toml` doesn't expose what the user wants, say so plainly. Don't invent fields.
## Preflight
- No `{project-root}/_bmad/` → BMad isn't installed. Say so, stop.
- `{project-root}/_bmad/scripts/resolve_customization.py` missing → continue, but Step 6 verify falls back to manual merge.
- Both present → proceed.
## Activation
Load `_bmad/config.toml` and `_bmad/config.user.toml` from `{project-root}` for `user_name` (default `BMad`) and `communication_language` (default `English`). Greet. If the user's invocation already names a target skill AND a specific change, jump to Step 3.
## Step 1: Classify intent
- **Directed** — specific skill + specific change → Step 3.
- **Exploratory** — "what can I customize?" → Step 2.
- **Audit/iterate** — wants to review or change something already customized → Step 2, lead with skills that have existing overrides; read the existing override in Step 3 before composing.
- **Cross-cutting** — could live on multiple surfaces → Step 3, choose agent vs workflow explicitly with the user.
## Step 2: Discovery
```
python3 {skill-root}/scripts/list_customizable_skills.py --project-root {project-root}
```
Use `--extra-root <path>` (repeatable) if the user has skills installed in additional locations.
Group the returned `agents` and `workflows` for the user; for each show name, description, whether `has_team_override` or `has_user_override` is true. Surface any `errors[]`. For audit/iterate intents, lead with already-overridden entries.
Empty list: show `scanned_roots`, ask whether skills live elsewhere (offer `--extra-root`); otherwise stop.
## Step 3: Determine the right surface
Read the target's `customize.toml`. Top-level `[agent]` or `[workflow]` block defines the surface.
If a team or user override already exists, read it first and summarize what's already overridden before composing.
**Cross-cutting intent — walk both surfaces with the user:**
- Every workflow a given agent runs → agent surface (e.g. `bmad-agent-pm.toml` with `persistent_facts`, `principles`).
- One workflow only → workflow surface (e.g. `bmad-create-prd.toml` with `activation_steps_prepend`).
- Several specific workflows → multiple workflow overrides in sequence, not an agent override.
**Single-surface heuristic:**
- Workflow-level: template swap, output path, step-specific behavior, or a named scalar already exposed (`*_template`, `on_complete`). Surgical, reliable.
- Agent-level: persona, communication style, org-wide facts, menu changes, behavior that should apply to every workflow the agent dispatches.
When ambiguous, present both with tradeoff, recommend one, let the user decide.
Intent outside the exposed surface (step logic, ordering, anything not in `customize.toml`): say so; offer `activation_steps_prepend`/`append` or `persistent_facts` as approximations, or recommend `bmad-builder` to create a custom skill.
## Step 4: Compose the override
Translate plain-English into TOML against the target's `customize.toml` fields. If an existing override was read, frame the change as additive.
Merge semantics:
- **Scalars** (`icon`, `role`, `*_template`, `on_complete`) — override wins.
- **Append arrays** (`persistent_facts`, `activation_steps_prepend`/`append`, `principles`) — team/user entries append in order.
- **Keyed arrays of tables** (menu items with `code` or `id`) — matching keys replace, new keys append.
Overrides are sparse: only the fields being changed. Never copy the whole `customize.toml`.
**Template swap** (`*_template` scalar): offer to copy the default template to `{project-root}/_bmad/custom/{skill-name}-{purpose}-template.md`, point the override at the new path, offer to help edit it.
## Step 5: Team or user placement
Under `{project-root}/_bmad/custom/`:
- `{skill-name}.toml` — team, committed. Policies, org conventions, compliance.
- `{skill-name}.user.toml` — user, gitignored. Personal tone, private facts, shortcuts.
Default by character (policy → team, personal → user), confirm before writing.
## Step 6: Show, confirm, write, verify
1. Show the full TOML. If the file exists, show a diff. Never silently overwrite.
2. Wait for explicit yes.
3. Write. Create `{project-root}/_bmad/custom/` if needed.
4. Verify:
```
python3 {project-root}/_bmad/scripts/resolve_customization.py --skill <install-path> --key <agent-or-workflow>
```
Show the merged output, point out the changed fields.
**Resolver missing or fails:** read whichever layers exist — `<install-path>/customize.toml` (base), `{project-root}/_bmad/custom/{skill-name}.toml` (team), `{project-root}/_bmad/custom/{skill-name}.user.toml` (user) — apply base → team → user with the same merge rules (scalars override, tables deep-merge, `code`/`id`-keyed arrays merge by key, all other arrays append), describe how the changed fields resolve.
**Verify shows override didn't land** (field unchanged, merge conflict, file not picked up): re-enter Step 4 with the verify output as context. Usually wrong field name, wrong merge mode (scalar vs array), or wrong scope.
5. Summarize what changed, where the file lives, how to iterate. Remind the user to commit team overrides.
## Complete when
- Override file written (or user explicitly aborted).
- User has seen resolver output (or manual fallback merge summary).
- User has acknowledged the summary.
Otherwise the skill isn't done — finish or tell the user they're exiting incomplete.
## When this skill can't help
- **Central config** (`{project-root}/_bmad/custom/config.toml`) — see the [How to Customize BMad guide](https://docs.bmad-method.org/how-to/customize-bmad/).
- **Step logic, ordering, behavior not in `customize.toml`** — open a feature request, or use `bmad-builder` to create a custom skill. Offer to help with either.
- **Skills without a `customize.toml`** — not customizable.

View File

@ -0,0 +1,231 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# ///
"""Enumerate customizable BMad skills installed alongside this one.
Scans a skills directory (by default: the directory this script's own skill
lives in, derived from __file__), finds every sibling directory containing a
`customize.toml`, classifies each as agent and/or workflow based on its
top-level blocks, reads the skill's SKILL.md frontmatter description for a
one-liner, and checks whether override files already exist in
`{project-root}/_bmad/custom/`.
Skills in BMad are loaded either from a project-local location (e.g. the
project's `.claude/skills/` or `.cursor/skills/`) or from a user-global
location (e.g. `~/.claude/skills/`). We do not hardcode those paths the
running skill's own location is the source of truth for sibling discovery.
`--extra-root` is available for the rare case where skills live in multiple
locations on the same machine.
Output: JSON to stdout. Non-empty `errors[]` in the payload is non-fatal
by contract the scanner surfaces malformed TOML, missing roots, and
skills with no customization block as data for the caller to display,
and still exits 0. Exit 2 is reserved for invocation errors (e.g.
missing or unreadable `--project-root`) where no useful payload can be
produced.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import tomllib
from pathlib import Path
# Top-level TOML blocks that indicate a customization surface.
SURFACE_KEYS = ("agent", "workflow")
FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
def default_skills_root() -> Path:
"""Derive the skills root from this script's location.
Layout assumption: {skills_root}/bmad-customize/scripts/list_customizable_skills.py.
So the skills root is three parents up from this file.
"""
return Path(__file__).resolve().parent.parent.parent
def read_frontmatter_description(skill_md: Path) -> str:
"""Extract the `description:` value from a SKILL.md YAML frontmatter block.
Returns an empty string if the file is missing, unreadable, or has no
description field. Intentionally permissive this is metadata for a
human-facing list, not a validation target.
"""
if not skill_md.is_file():
return ""
try:
text = skill_md.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
return ""
m = FRONTMATTER_RE.match(text)
if not m:
return ""
for line in m.group(1).splitlines():
stripped = line.strip()
if stripped.startswith("description:"):
value = stripped[len("description:") :].strip()
# Strip surrounding quotes if present.
if (value.startswith("'") and value.endswith("'")) or (
value.startswith('"') and value.endswith('"')
):
value = value[1:-1]
return value
return ""
def load_customize(toml_path: Path) -> dict | None:
"""Return the parsed TOML, or None if unreadable."""
try:
with toml_path.open("rb") as f:
return tomllib.load(f)
except (OSError, tomllib.TOMLDecodeError):
return None
def scan_skills(
skills_roots: list[Path],
project_root: Path,
) -> dict:
"""Scan each skills root for directories that contain a customize.toml."""
agents: list[dict] = []
workflows: list[dict] = []
errors: list[str] = []
scanned_roots: list[str] = []
seen_names: set[str] = set()
custom_dir = project_root / "_bmad" / "custom"
for root in skills_roots:
if not root.is_dir():
errors.append(f"skills root does not exist: {root}")
continue
scanned_roots.append(str(root))
for skill_dir in sorted(p for p in root.iterdir() if p.is_dir()):
customize_toml = skill_dir / "customize.toml"
if not customize_toml.is_file():
continue
data = load_customize(customize_toml)
if data is None:
errors.append(f"failed to parse {customize_toml}")
continue
skill_name = skill_dir.name
# If a skill with this name was already found in an earlier
# root, skip it — roots are scanned in the order provided, so
# the first occurrence wins.
if skill_name in seen_names:
continue
seen_names.add(skill_name)
description = read_frontmatter_description(skill_dir / "SKILL.md")
team_override = custom_dir / f"{skill_name}.toml"
user_override = custom_dir / f"{skill_name}.user.toml"
entry_base = {
"name": skill_name,
"install_path": str(skill_dir),
"skills_root": str(root),
"description": description,
"has_team_override": team_override.is_file(),
"has_user_override": user_override.is_file(),
"team_override_path": str(team_override),
"user_override_path": str(user_override),
}
# A skill may expose an agent surface, a workflow surface, or
# both. Emit one entry per surface so the caller can group cleanly.
surfaces_found = [k for k in SURFACE_KEYS if k in data]
if not surfaces_found:
errors.append(
f"no [agent] or [workflow] block in {customize_toml}"
)
continue
for surface in surfaces_found:
entry = dict(entry_base)
entry["surface"] = surface
if surface == "agent":
agents.append(entry)
else:
workflows.append(entry)
return {
"project_root": str(project_root),
"scanned_roots": scanned_roots,
"custom_dir": str(custom_dir),
"agents": agents,
"workflows": workflows,
"errors": errors,
}
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"List customizable BMad skills installed alongside this one, "
"grouped by surface (agent vs workflow), with override status "
"looked up against {project-root}/_bmad/custom/."
)
)
parser.add_argument(
"--project-root",
required=True,
help="Absolute path to the project root (the folder containing _bmad/).",
)
parser.add_argument(
"--skills-root",
default=None,
help=(
"Override the primary skills directory to scan. Defaults to the "
"directory this script's own skill lives in."
),
)
parser.add_argument(
"--extra-root",
action="append",
default=[],
metavar="PATH",
help=(
"Additional skills directory to include (repeatable). Useful "
"when skills live in multiple locations on the same machine "
"(e.g. project-local plus a user-global install)."
),
)
return parser.parse_args(argv)
def main(argv: list[str]) -> int:
args = parse_args(argv)
project_root = Path(args.project_root).expanduser().resolve()
if not project_root.is_dir():
print(
f"error: project-root does not exist or is not a directory: {project_root}",
file=sys.stderr,
)
return 2
primary = (
Path(args.skills_root).expanduser().resolve()
if args.skills_root
else default_skills_root()
)
extras = [Path(p).expanduser().resolve() for p in args.extra_root]
# Deduplicate in order of appearance.
roots: list[Path] = []
for root in [primary, *extras]:
if root not in roots:
roots.append(root)
result = scan_skills(roots, project_root)
print(json.dumps(result, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

View File

@ -0,0 +1,249 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# ///
"""Unit tests for list_customizable_skills.py.
Exercises the scanner against a synthesized install tree:
- an agent-only customize.toml
- a workflow-only customize.toml
- a customize.toml that exposes both surfaces
- a skill directory with no customize.toml (ignored)
- a pre-existing team override in _bmad/custom/
- malformed TOML (surfaces as an error without aborting)
- multiple skills roots (e.g. project-local + user-global mix)
Run: python3 scripts/tests/test_list_customizable_skills.py
"""
from __future__ import annotations
import importlib.util
import json
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
SCRIPT = Path(__file__).resolve().parent.parent / "list_customizable_skills.py"
def _load_module():
spec = importlib.util.spec_from_file_location("list_customizable_skills", SCRIPT)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore[union-attr]
return module
MODULE = _load_module()
def _make_skill(parent: Path, name: str, body: str, skill_md: str | None = None) -> Path:
skill_dir = parent / name
skill_dir.mkdir(parents=True, exist_ok=True)
(skill_dir / "customize.toml").write_text(body, encoding="utf-8")
if skill_md is not None:
(skill_dir / "SKILL.md").write_text(skill_md, encoding="utf-8")
return skill_dir
class ScannerTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.root = Path(self.tmp.name)
self.skills = self.root / "skills"
self.skills.mkdir(parents=True)
self.custom = self.root / "_bmad" / "custom"
self.custom.mkdir(parents=True)
def tearDown(self):
self.tmp.cleanup()
def test_agent_only_skill_detected(self):
_make_skill(
self.skills,
"bmad-agent-pm",
"[agent]\nicon = \"🧠\"\n",
"---\nname: bmad-agent-pm\ndescription: Product manager.\n---\n",
)
result = MODULE.scan_skills([self.skills], self.root)
self.assertEqual(len(result["agents"]), 1)
self.assertEqual(len(result["workflows"]), 0)
entry = result["agents"][0]
self.assertEqual(entry["name"], "bmad-agent-pm")
self.assertEqual(entry["surface"], "agent")
self.assertEqual(entry["description"], "Product manager.")
self.assertFalse(entry["has_team_override"])
self.assertFalse(entry["has_user_override"])
def test_workflow_only_skill_detected(self):
_make_skill(
self.skills,
"bmad-create-prd",
"[workflow]\npersistent_facts = []\n",
"---\nname: bmad-create-prd\ndescription: 'Create a PRD.'\n---\n",
)
result = MODULE.scan_skills([self.skills], self.root)
self.assertEqual(len(result["agents"]), 0)
self.assertEqual(len(result["workflows"]), 1)
entry = result["workflows"][0]
self.assertEqual(entry["description"], "Create a PRD.")
def test_dual_surface_skill_emits_two_entries(self):
_make_skill(
self.skills,
"bmad-dual",
"[agent]\nicon = \"x\"\n\n[workflow]\npersistent_facts = []\n",
"---\nname: bmad-dual\ndescription: Dual.\n---\n",
)
result = MODULE.scan_skills([self.skills], self.root)
self.assertEqual(len(result["agents"]), 1)
self.assertEqual(len(result["workflows"]), 1)
self.assertEqual(result["agents"][0]["name"], "bmad-dual")
self.assertEqual(result["workflows"][0]["name"], "bmad-dual")
def test_skill_without_customize_toml_ignored(self):
(self.skills / "bmad-plain").mkdir()
(self.skills / "bmad-plain" / "SKILL.md").write_text("# plain\n")
result = MODULE.scan_skills([self.skills], self.root)
self.assertEqual(len(result["agents"]) + len(result["workflows"]), 0)
self.assertEqual(result["errors"], [])
def test_existing_team_override_flagged(self):
_make_skill(
self.skills,
"bmad-agent-pm",
"[agent]\nicon = \"x\"\n",
"---\nname: bmad-agent-pm\ndescription: PM.\n---\n",
)
(self.custom / "bmad-agent-pm.toml").write_text("[agent]\n")
result = MODULE.scan_skills([self.skills], self.root)
entry = result["agents"][0]
self.assertTrue(entry["has_team_override"])
self.assertFalse(entry["has_user_override"])
def test_missing_surface_block_reports_error(self):
_make_skill(self.skills, "bmad-broken", "[not_a_surface]\nfoo = 1\n")
result = MODULE.scan_skills([self.skills], self.root)
self.assertEqual(len(result["agents"]) + len(result["workflows"]), 0)
self.assertEqual(len(result["errors"]), 1)
self.assertIn("no [agent] or [workflow] block", result["errors"][0])
def test_malformed_toml_reports_error_without_aborting(self):
skill_dir = self.skills / "bmad-bad"
skill_dir.mkdir()
(skill_dir / "customize.toml").write_text("this is not [valid toml\n")
# Plus a good sibling to confirm scanning continues.
_make_skill(
self.skills,
"bmad-good",
"[agent]\nicon = \"x\"\n",
"---\nname: bmad-good\ndescription: Good.\n---\n",
)
result = MODULE.scan_skills([self.skills], self.root)
self.assertEqual(len(result["agents"]), 1)
self.assertEqual(result["agents"][0]["name"], "bmad-good")
self.assertTrue(any("failed to parse" in e for e in result["errors"]))
def test_description_with_double_quotes_stripped(self):
_make_skill(
self.skills,
"bmad-q",
"[agent]\nicon = \"x\"\n",
'---\nname: bmad-q\ndescription: "Double-quoted desc."\n---\n',
)
result = MODULE.scan_skills([self.skills], self.root)
self.assertEqual(result["agents"][0]["description"], "Double-quoted desc.")
def test_multiple_skills_roots_are_merged(self):
extra_root = self.root / "extra-skills"
extra_root.mkdir()
_make_skill(
self.skills,
"bmad-agent-pm",
"[agent]\nicon = \"x\"\n",
"---\nname: bmad-agent-pm\ndescription: PM.\n---\n",
)
_make_skill(
extra_root,
"bmad-agent-dev",
"[agent]\nicon = \"y\"\n",
"---\nname: bmad-agent-dev\ndescription: Dev.\n---\n",
)
result = MODULE.scan_skills([self.skills, extra_root], self.root)
names = {a["name"] for a in result["agents"]}
self.assertEqual(names, {"bmad-agent-pm", "bmad-agent-dev"})
self.assertEqual(len(result["scanned_roots"]), 2)
def test_duplicate_skill_name_across_roots_first_wins(self):
extra_root = self.root / "extra-skills"
extra_root.mkdir()
_make_skill(
self.skills,
"bmad-agent-pm",
"[agent]\nicon = \"primary\"\n",
"---\nname: bmad-agent-pm\ndescription: Primary.\n---\n",
)
_make_skill(
extra_root,
"bmad-agent-pm",
"[agent]\nicon = \"duplicate\"\n",
"---\nname: bmad-agent-pm\ndescription: Duplicate.\n---\n",
)
result = MODULE.scan_skills([self.skills, extra_root], self.root)
self.assertEqual(len(result["agents"]), 1)
self.assertEqual(result["agents"][0]["description"], "Primary.")
self.assertEqual(result["agents"][0]["skills_root"], str(self.skills))
def test_missing_skills_root_reports_error(self):
result = MODULE.scan_skills(
[self.root / "does-not-exist", self.skills],
self.root,
)
self.assertTrue(any("skills root does not exist" in e for e in result["errors"]))
def test_cli_emits_valid_json_and_exits_zero(self):
_make_skill(
self.skills,
"bmad-agent-pm",
"[agent]\nicon = \"x\"\n",
"---\nname: bmad-agent-pm\ndescription: PM.\n---\n",
)
proc = subprocess.run(
[
sys.executable,
str(SCRIPT),
"--project-root",
str(self.root),
"--skills-root",
str(self.skills),
],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(proc.returncode, 0, proc.stderr)
payload = json.loads(proc.stdout)
self.assertEqual(len(payload["agents"]), 1)
def test_cli_exits_two_on_missing_project_root(self):
proc = subprocess.run(
[
sys.executable,
str(SCRIPT),
"--project-root",
str(self.root / "does-not-exist"),
"--skills-root",
str(self.skills),
],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(proc.returncode, 2)
self.assertIn("does not exist", proc.stderr)
if __name__ == "__main__":
unittest.main()

View File

@ -10,3 +10,4 @@ Core,bmad-editorial-review-structure,Editorial Review - Structure,ES,Use when do
Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assurance or before finalizing deliverables. Code Review in other modules runs this automatically, but also useful for document reviews.",[path],anytime,,,false,,
Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,[path],anytime,,,false,,
Core,bmad-distillator,Distillator,DG,Use when you need token-efficient distillates that preserve all information for downstream LLM consumption.,[path],anytime,,,false,adjacent to source document or specified output_path,distillate markdown file(s)
Core,bmad-customize,BMad Customize,BC,"Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required.",,anytime,,,false,{project-root}/_bmad/custom,TOML override files

Can't render this file because it has a wrong number of fields in line 3.

View File

@ -2256,6 +2256,374 @@ async function runTests() {
console.log('');
// ============================================================
// Test Suite 38: External-Module Agent Resolution
// ============================================================
console.log(`${colors.yellow}Test Suite 38: External-Module Agent Resolution${colors.reset}\n`);
{
// Scenario: external official modules (bmb, cis, gds, ...) are cloned into
// ~/.bmad/cache/external-modules/<name>/ — NOT copied into src/modules/.
// collectAgentsFromModuleYaml must resolve them from the cache or their
// agent roster silently vanishes from config.toml.
const tempCacheDir38 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-ext-cache-'));
const tempBmadDir38 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-ext-install-'));
const priorCacheEnv = process.env.BMAD_EXTERNAL_MODULES_CACHE;
process.env.BMAD_EXTERNAL_MODULES_CACHE = tempCacheDir38;
try {
// Seed a fake external module with agents at cache/<mod>/src/module.yaml —
// matches the real CIS layout.
const extSrcDir = path.join(tempCacheDir38, 'fake-ext', 'src');
await fs.ensureDir(extSrcDir);
await fs.writeFile(
path.join(extSrcDir, 'module.yaml'),
[
'code: fake-ext',
'name: "Fake External Module"',
'agents:',
' - code: bmad-fake-ext-agent-one',
' name: Ext-One',
' title: External Agent One',
' icon: "🧪"',
' team: fake',
' description: "First fake external agent."',
' - code: bmad-fake-ext-agent-two',
' name: Ext-Two',
' title: External Agent Two',
' icon: "🧬"',
' team: fake',
' description: "Second fake external agent."',
'',
].join('\n'),
);
// Second fake module at cache/<mod>/skills/module.yaml — matches bmb layout.
const extSkillsDir = path.join(tempCacheDir38, 'fake-skills', 'skills');
await fs.ensureDir(extSkillsDir);
await fs.writeFile(
path.join(extSkillsDir, 'module.yaml'),
[
'code: fake-skills',
'name: "Fake Skills-Layout Module"',
'agents:',
' - code: bmad-fake-skills-agent',
' name: SkillsHero',
' title: Skills Layout Agent',
' icon: "🛠️"',
' team: fake-skills',
' description: "Lives under skills/ not src/."',
'',
].join('\n'),
);
const generator38 = new ManifestGenerator();
generator38.bmadDir = tempBmadDir38;
generator38.bmadFolderName = path.basename(tempBmadDir38);
generator38.updatedModules = ['core', 'bmm', 'fake-ext', 'fake-skills'];
await generator38.collectAgentsFromModuleYaml();
const byCode = new Map(generator38.agents.map((a) => [a.code, a]));
assert(byCode.has('bmad-fake-ext-agent-one'), 'external module at cache/<name>/src resolves and contributes agent one');
assert(byCode.has('bmad-fake-ext-agent-two'), 'external module at cache/<name>/src resolves and contributes agent two');
assert(byCode.has('bmad-fake-skills-agent'), 'external module at cache/<name>/skills layout also resolves');
assert(byCode.get('bmad-fake-ext-agent-one').module === 'fake-ext', 'agent.module matches the owning external module name');
assert(byCode.get('bmad-fake-ext-agent-one').team === 'fake', 'explicit team from module.yaml is preserved');
await generator38.writeCentralConfig(tempBmadDir38, {
core: {},
bmm: {},
'fake-ext': {},
'fake-skills': {},
});
const teamContent = await fs.readFile(path.join(tempBmadDir38, 'config.toml'), 'utf8');
assert(teamContent.includes('[agents.bmad-fake-ext-agent-one]'), 'external-module agents land in config.toml [agents.*] section');
assert(teamContent.includes('[agents.bmad-fake-skills-agent]'), 'skills-layout external module agents also land in config.toml');
assert(teamContent.includes('First fake external agent.'), 'agent description from external module.yaml is written');
} finally {
if (priorCacheEnv === undefined) {
delete process.env.BMAD_EXTERNAL_MODULES_CACHE;
} else {
process.env.BMAD_EXTERNAL_MODULES_CACHE = priorCacheEnv;
}
await fs.remove(tempCacheDir38).catch(() => {});
await fs.remove(tempBmadDir38).catch(() => {});
}
}
console.log('');
// ============================================================
// Test Suite 39: Module Version Resolution
// ============================================================
console.log(`${colors.yellow}Test Suite 39: Module Version Resolution${colors.reset}\n`);
// --- package.json beats module.yaml and marketplace.json for cached external modules ---
{
const { resolveModuleVersion } = require('../tools/installer/modules/version-resolver');
const tempCacheDir39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-version-cache-'));
const priorCacheEnv39 = process.env.BMAD_EXTERNAL_MODULES_CACHE;
process.env.BMAD_EXTERNAL_MODULES_CACHE = tempCacheDir39;
try {
const moduleRoot = path.join(tempCacheDir39, 'tea');
const moduleSrc = path.join(moduleRoot, 'src');
await fs.ensureDir(path.join(moduleRoot, '.claude-plugin'));
await fs.ensureDir(moduleSrc);
await fs.writeFile(
path.join(moduleRoot, 'package.json'),
JSON.stringify({ name: 'bmad-method-test-architecture-enterprise', version: '1.12.3' }, null, 2) + '\n',
);
await fs.writeFile(
path.join(moduleSrc, 'module.yaml'),
['code: tea', 'name: Test Architect', 'module_version: 1.11.0', ''].join('\n'),
);
await fs.writeFile(
path.join(moduleRoot, '.claude-plugin', 'marketplace.json'),
JSON.stringify({ plugins: [{ name: 'tea', version: '1.7.2' }] }, null, 2) + '\n',
);
const versionInfo = await resolveModuleVersion('tea');
assert(versionInfo.version === '1.12.3', 'resolver prefers cached package.json over stale marketplace metadata for external modules');
assert(versionInfo.source === 'package.json', 'resolver reports package.json as the winning metadata source');
} finally {
if (priorCacheEnv39 === undefined) {
delete process.env.BMAD_EXTERNAL_MODULES_CACHE;
} else {
process.env.BMAD_EXTERNAL_MODULES_CACHE = priorCacheEnv39;
}
await fs.remove(tempCacheDir39).catch(() => {});
}
}
// --- module.yaml is used when package.json is absent ---
{
const { resolveModuleVersion } = require('../tools/installer/modules/version-resolver');
const tempRepo39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-version-module-yaml-'));
const tempCacheDir39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-version-module-yaml-cache-'));
const priorCacheEnv39 = process.env.BMAD_EXTERNAL_MODULES_CACHE;
process.env.BMAD_EXTERNAL_MODULES_CACHE = tempCacheDir39;
try {
const moduleDir = path.join(tempRepo39, 'src');
await fs.ensureDir(path.join(tempRepo39, '.claude-plugin'));
await fs.ensureDir(moduleDir);
await fs.writeFile(path.join(moduleDir, 'module.yaml'), ['code: sample-mod', 'module_version: 2.4.0', ''].join('\n'));
await fs.writeFile(
path.join(tempRepo39, '.claude-plugin', 'marketplace.json'),
JSON.stringify({ plugins: [{ name: 'sample-mod', version: '1.7.2' }] }, null, 2) + '\n',
);
const versionInfo = await resolveModuleVersion('sample-mod', { moduleSourcePath: moduleDir });
assert(versionInfo.version === '2.4.0', 'resolver falls back to module.yaml when package.json is missing');
assert(versionInfo.source === 'module.yaml', 'resolver reports module.yaml when it provides the selected version');
} finally {
if (priorCacheEnv39 === undefined) {
delete process.env.BMAD_EXTERNAL_MODULES_CACHE;
} else {
process.env.BMAD_EXTERNAL_MODULES_CACHE = priorCacheEnv39;
}
await fs.remove(tempRepo39).catch(() => {});
await fs.remove(tempCacheDir39).catch(() => {});
}
}
// --- marketplace fallback uses semver-aware comparison ---
{
const { resolveModuleVersion } = require('../tools/installer/modules/version-resolver');
const tempRepo39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-version-marketplace-'));
const tempCacheDir39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-version-marketplace-cache-'));
const priorCacheEnv39 = process.env.BMAD_EXTERNAL_MODULES_CACHE;
process.env.BMAD_EXTERNAL_MODULES_CACHE = tempCacheDir39;
try {
const moduleDir = path.join(tempRepo39, 'src');
await fs.ensureDir(path.join(tempRepo39, '.claude-plugin'));
await fs.ensureDir(moduleDir);
await fs.writeFile(
path.join(tempRepo39, '.claude-plugin', 'marketplace.json'),
JSON.stringify(
{
plugins: [
{ name: 'older-plugin', version: '1.7.2' },
{ name: 'newer-plugin', version: '1.12.3' },
],
},
null,
2,
) + '\n',
);
const versionInfo = await resolveModuleVersion('missing-plugin', { moduleSourcePath: moduleDir });
assert(
versionInfo.version === '1.12.3',
'resolver picks the highest marketplace fallback version using semver instead of string comparison',
);
assert(versionInfo.source === 'marketplace.json', 'resolver reports marketplace.json when it is the only usable metadata source');
} finally {
if (priorCacheEnv39 === undefined) {
delete process.env.BMAD_EXTERNAL_MODULES_CACHE;
} else {
process.env.BMAD_EXTERNAL_MODULES_CACHE = priorCacheEnv39;
}
await fs.remove(tempRepo39).catch(() => {});
await fs.remove(tempCacheDir39).catch(() => {});
}
}
// --- package.json lookup must not escape the module repo boundary ---
{
const { resolveModuleVersion } = require('../tools/installer/modules/version-resolver');
const tempHost39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-version-boundary-host-'));
const tempCacheDir39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-version-boundary-cache-'));
const priorCacheEnv39 = process.env.BMAD_EXTERNAL_MODULES_CACHE;
process.env.BMAD_EXTERNAL_MODULES_CACHE = tempCacheDir39;
try {
const moduleRoot = path.join(tempHost39, 'nested-module');
const moduleDir = path.join(moduleRoot, 'src');
await fs.ensureDir(path.join(moduleRoot, '.claude-plugin'));
await fs.ensureDir(moduleDir);
await fs.writeFile(path.join(tempHost39, 'package.json'), JSON.stringify({ name: 'host-project', version: '9.9.9' }, null, 2) + '\n');
await fs.writeFile(path.join(moduleDir, 'module.yaml'), ['code: sample-mod', 'module_version: 2.4.0', ''].join('\n'));
await fs.writeFile(
path.join(moduleRoot, '.claude-plugin', 'marketplace.json'),
JSON.stringify({ plugins: [{ name: 'sample-mod', version: '1.7.2' }] }, null, 2) + '\n',
);
const versionInfo = await resolveModuleVersion('sample-mod', { moduleSourcePath: moduleDir });
assert(versionInfo.version === '2.4.0', 'resolver does not read a host project package.json outside the module repo boundary');
assert(versionInfo.source === 'module.yaml', 'resolver stops at the module repo boundary before climbing into host project metadata');
} finally {
if (priorCacheEnv39 === undefined) {
delete process.env.BMAD_EXTERNAL_MODULES_CACHE;
} else {
process.env.BMAD_EXTERNAL_MODULES_CACHE = priorCacheEnv39;
}
await fs.remove(tempHost39).catch(() => {});
await fs.remove(tempCacheDir39).catch(() => {});
}
}
// --- Manifest uses the shared resolver for external modules ---
{
const { Manifest } = require('../tools/installer/core/manifest');
const { ExternalModuleManager } = require('../tools/installer/modules/external-manager');
const tempCacheDir39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-manifest-version-cache-'));
const tempBmadDir39 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-manifest-version-install-'));
const priorCacheEnv39 = process.env.BMAD_EXTERNAL_MODULES_CACHE;
const originalLoadConfig39 = ExternalModuleManager.prototype.loadExternalModulesConfig;
process.env.BMAD_EXTERNAL_MODULES_CACHE = tempCacheDir39;
ExternalModuleManager.prototype.loadExternalModulesConfig = async function () {
return {
modules: [
{
code: 'tea',
name: 'Test Architect',
repository: 'https://example.com/tea.git',
module_definition: 'src/module.yaml',
npm_package: 'bmad-method-test-architecture-enterprise',
},
],
};
};
try {
const moduleRoot = path.join(tempCacheDir39, 'tea');
const moduleSrc = path.join(moduleRoot, 'src');
await fs.ensureDir(path.join(moduleRoot, '.claude-plugin'));
await fs.ensureDir(moduleSrc);
await fs.writeFile(
path.join(moduleRoot, 'package.json'),
JSON.stringify({ name: 'bmad-method-test-architecture-enterprise', version: '1.12.3' }, null, 2) + '\n',
);
await fs.writeFile(path.join(moduleSrc, 'module.yaml'), ['code: tea', 'module_version: 1.11.0', ''].join('\n'));
await fs.writeFile(
path.join(moduleRoot, '.claude-plugin', 'marketplace.json'),
JSON.stringify({ plugins: [{ name: 'tea', version: '1.7.2' }] }, null, 2) + '\n',
);
const manifest39 = new Manifest();
const versionInfo = await manifest39.getModuleVersionInfo('tea', tempBmadDir39, moduleSrc);
assert(versionInfo.version === '1.12.3', 'manifest version info prefers external package.json over stale marketplace metadata');
assert(versionInfo.source === 'external', 'manifest preserves external source classification while using the shared resolver');
assert(
versionInfo.npmPackage === 'bmad-method-test-architecture-enterprise',
'manifest preserves npm package metadata for external modules',
);
} finally {
ExternalModuleManager.prototype.loadExternalModulesConfig = originalLoadConfig39;
if (priorCacheEnv39 === undefined) {
delete process.env.BMAD_EXTERNAL_MODULES_CACHE;
} else {
process.env.BMAD_EXTERNAL_MODULES_CACHE = priorCacheEnv39;
}
await fs.remove(tempCacheDir39).catch(() => {});
await fs.remove(tempBmadDir39).catch(() => {});
}
}
// --- Update checks should not advertise npm downgrades when source installs are newer ---
{
const { Manifest } = require('../tools/installer/core/manifest');
const manifest39 = new Manifest();
const originalGetAllModuleVersions39 = manifest39.getAllModuleVersions.bind(manifest39);
const originalFetchNpmVersion39 = manifest39.fetchNpmVersion.bind(manifest39);
manifest39.getAllModuleVersions = async () => [
{
name: 'tea',
version: '1.12.3',
npmPackage: 'bmad-method-test-architecture-enterprise',
},
];
manifest39.fetchNpmVersion = async () => '1.7.2';
try {
const updates = await manifest39.checkForUpdates('/unused');
assert(updates.length === 0, 'update check ignores older npm versions when installed source metadata is newer');
} finally {
manifest39.getAllModuleVersions = originalGetAllModuleVersions39;
manifest39.fetchNpmVersion = originalFetchNpmVersion39;
}
}
// --- Update checks ignore non-semver version strings instead of flagging false positives ---
{
const { Manifest } = require('../tools/installer/core/manifest');
const manifest39 = new Manifest();
const originalGetAllModuleVersions39 = manifest39.getAllModuleVersions.bind(manifest39);
const originalFetchNpmVersion39 = manifest39.fetchNpmVersion.bind(manifest39);
manifest39.getAllModuleVersions = async () => [
{
name: 'tea',
version: 'workspace-build',
npmPackage: 'bmad-method-test-architecture-enterprise',
},
];
manifest39.fetchNpmVersion = async () => 'latest-build';
try {
const updates = await manifest39.checkForUpdates('/unused');
assert(updates.length === 0, 'update check ignores non-semver version strings instead of reporting misleading updates');
} finally {
manifest39.getAllModuleVersions = originalGetAllModuleVersions39;
manifest39.fetchNpmVersion = originalFetchNpmVersion39;
}
}
console.log('');
// ============================================================
// Summary
// ============================================================

View File

@ -0,0 +1,348 @@
/**
* Installer Channel Resolution Tests
*
* Unit tests for the pure planning/resolution modules:
* - tools/installer/modules/channel-plan.js
* - tools/installer/modules/channel-resolver.js
*
* Neither module does I/O outside of GitHub tag lookups (which we don't
* exercise here) and semver math. All tests are deterministic.
*
* Usage: node test/test-installer-channels.js
*/
const {
parseChannelOptions,
decideChannelForModule,
buildPlan,
orphanPinWarnings,
bundledTargetWarnings,
parsePinSpec,
} = require('../tools/installer/modules/channel-plan');
const { parseGitHubRepo, normalizeStableTag, classifyUpgrade, releaseNotesUrl } = require('../tools/installer/modules/channel-resolver');
const colors = {
reset: '',
green: '',
red: '',
yellow: '',
cyan: '',
dim: '',
};
let passed = 0;
let failed = 0;
function assert(condition, testName, errorMessage = '') {
if (condition) {
console.log(`${colors.green}${colors.reset} ${testName}`);
passed++;
} else {
console.log(`${colors.red}${colors.reset} ${testName}`);
if (errorMessage) {
console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
}
failed++;
}
}
function assertEqual(actual, expected, testName) {
const ok = actual === expected;
assert(ok, testName, ok ? '' : `expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
}
function section(title) {
console.log(`\n${colors.cyan}── ${title} ──${colors.reset}`);
}
function runTests() {
// ─────────────────────────────────────────────────────────────────────────
// channel-plan.js :: parsePinSpec
// ─────────────────────────────────────────────────────────────────────────
section('channel-plan :: parsePinSpec');
{
const r = parsePinSpec('bmb=v1.2.3');
assert(r && r.code === 'bmb' && r.tag === 'v1.2.3', 'valid CODE=TAG');
}
{
const r = parsePinSpec(' cis = v0.1.0 ');
assert(r && r.code === 'cis' && r.tag === 'v0.1.0', 'trims whitespace around code and tag');
}
assert(parsePinSpec('') === null, 'empty string returns null');
assert(parsePinSpec('bmb') === null, 'missing = returns null');
assert(parsePinSpec('=v1.0.0') === null, 'leading = returns null');
assert(parsePinSpec('bmb=') === null, 'trailing = returns null');
assert(parsePinSpec(null) === null, 'null input returns null');
let undef;
assert(parsePinSpec(undef) === null, 'undefined input returns null');
assert(parsePinSpec(42) === null, 'non-string input returns null');
// ─────────────────────────────────────────────────────────────────────────
// channel-plan.js :: parseChannelOptions
// ─────────────────────────────────────────────────────────────────────────
section('channel-plan :: parseChannelOptions');
{
const r = parseChannelOptions({});
assert(r.global === null, 'empty: global is null');
assert(r.nextSet instanceof Set && r.nextSet.size === 0, 'empty: nextSet is empty Set');
assert(r.pins instanceof Map && r.pins.size === 0, 'empty: pins is empty Map');
assert(Array.isArray(r.warnings) && r.warnings.length === 0, 'empty: no warnings');
assert(r.acceptBypass === false, 'empty: acceptBypass false by default');
}
{
const r = parseChannelOptions({ channel: 'stable' });
assertEqual(r.global, 'stable', '--channel=stable sets global');
}
{
const r = parseChannelOptions({ channel: 'NEXT' });
assertEqual(r.global, 'next', '--channel is case-insensitive');
}
{
const r = parseChannelOptions({ allStable: true });
assertEqual(r.global, 'stable', '--all-stable sets global stable');
}
{
const r = parseChannelOptions({ allNext: true });
assertEqual(r.global, 'next', '--all-next sets global next');
}
{
const r = parseChannelOptions({ channel: 'bogus' });
assert(r.global === null, 'invalid --channel value is rejected (global stays null)');
assert(
r.warnings.some((w) => w.includes("Ignoring invalid --channel value 'bogus'")),
'invalid --channel produces a warning',
);
}
{
// --all-stable and --all-next conflict → warning, first-wins
const r = parseChannelOptions({ allStable: true, allNext: true });
assertEqual(r.global, 'stable', 'conflict: first flag (--all-stable) wins');
assert(
r.warnings.some((w) => w.includes('Conflicting channel flags')),
'conflict produces warning',
);
}
{
const r = parseChannelOptions({ next: ['bmb', 'cis', ' '] });
assert(r.nextSet.has('bmb') && r.nextSet.has('cis'), '--next=CODE adds to nextSet');
assert(!r.nextSet.has(''), 'blank --next entries are skipped');
}
{
const r = parseChannelOptions({ pin: ['bmb=v1.0.0', 'cis=v2.0.0'] });
assertEqual(r.pins.get('bmb'), 'v1.0.0', '--pin bmb=v1.0.0 recorded');
assertEqual(r.pins.get('cis'), 'v2.0.0', '--pin cis=v2.0.0 recorded');
}
{
const r = parseChannelOptions({ pin: ['bmb=v1.0.0', 'bmb=v1.1.0'] });
assertEqual(r.pins.get('bmb'), 'v1.1.0', 'duplicate --pin: last wins');
assert(
r.warnings.some((w) => w.includes('--pin specified multiple times')),
'duplicate --pin produces warning',
);
}
{
const r = parseChannelOptions({ pin: ['malformed-no-equals'] });
assert(r.pins.size === 0, 'malformed --pin is ignored');
assert(
r.warnings.some((w) => w.includes('malformed --pin')),
'malformed --pin warns',
);
}
{
const r = parseChannelOptions({ yes: true });
assertEqual(r.acceptBypass, true, '--yes sets acceptBypass so curator-bypass prompt is auto-confirmed');
}
{
const r = parseChannelOptions({ acceptBypass: true });
assertEqual(r.acceptBypass, true, 'explicit acceptBypass: true honored');
}
// ─────────────────────────────────────────────────────────────────────────
// channel-plan.js :: decideChannelForModule (precedence)
// ─────────────────────────────────────────────────────────────────────────
section('channel-plan :: decideChannelForModule (precedence)');
const emptyOpts = parseChannelOptions({});
{
const r = decideChannelForModule({ code: 'bmb', channelOptions: emptyOpts });
assertEqual(r.channel, 'stable', 'no signal → stable default');
assertEqual(r.source, 'default', 'source: default');
}
{
const r = decideChannelForModule({ code: 'bmb', channelOptions: emptyOpts, registryDefault: 'next' });
assertEqual(r.channel, 'next', 'registry default applied when no flags');
assertEqual(r.source, 'registry', 'source: registry');
}
{
const r = decideChannelForModule({ code: 'bmb', channelOptions: emptyOpts, registryDefault: 'bogus' });
assertEqual(r.channel, 'stable', 'invalid registry default ignored, falls to stable');
}
{
const opts = parseChannelOptions({ channel: 'next' });
const r = decideChannelForModule({ code: 'bmb', channelOptions: opts, registryDefault: 'stable' });
assertEqual(r.channel, 'next', 'global --channel beats registry default');
assertEqual(r.source, 'flag:--channel', 'source reflects --channel origin');
}
{
const opts = parseChannelOptions({ channel: 'stable', next: ['bmb'] });
const r = decideChannelForModule({ code: 'bmb', channelOptions: opts });
assertEqual(r.channel, 'next', '--next=bmb beats --channel=stable for bmb');
assertEqual(r.source, 'flag:--next', 'source: flag:--next');
}
{
const opts = parseChannelOptions({ channel: 'next', pin: ['bmb=v1.0.0'] });
const r = decideChannelForModule({ code: 'bmb', channelOptions: opts });
assertEqual(r.channel, 'pinned', '--pin beats --channel');
assertEqual(r.pin, 'v1.0.0', 'pin value carried through');
assertEqual(r.source, 'flag:--pin', 'source: flag:--pin');
}
{
const opts = parseChannelOptions({ next: ['bmb'], pin: ['bmb=v1.0.0'] });
const r = decideChannelForModule({ code: 'bmb', channelOptions: opts });
assertEqual(r.channel, 'pinned', '--pin beats --next for same code');
}
// ─────────────────────────────────────────────────────────────────────────
// channel-plan.js :: buildPlan, orphanPinWarnings, bundledTargetWarnings
// ─────────────────────────────────────────────────────────────────────────
section('channel-plan :: buildPlan / warnings');
{
const opts = parseChannelOptions({ allStable: true, pin: ['bmb=v1.0.0'] });
const plan = buildPlan({
modules: [
{ code: 'bmb', defaultChannel: 'stable' },
{ code: 'cis', defaultChannel: 'stable' },
],
channelOptions: opts,
});
assertEqual(plan.get('bmb').channel, 'pinned', 'buildPlan: bmb pinned');
assertEqual(plan.get('cis').channel, 'stable', 'buildPlan: cis stable via global');
}
{
const opts = parseChannelOptions({ pin: ['ghost=v1.0.0', 'bmb=v1.0.0'], next: ['gds'] });
const warnings = orphanPinWarnings(opts, ['bmb']);
assert(
warnings.some((w) => w.includes("--pin for 'ghost'")),
'orphanPinWarnings: flags pin for unselected module',
);
assert(
warnings.some((w) => w.includes("--next for 'gds'")),
'orphanPinWarnings: flags --next for unselected module',
);
assert(!warnings.some((w) => w.includes("'bmb'")), 'orphanPinWarnings: no warning for selected module');
}
{
const opts = parseChannelOptions({ pin: ['bmm=v1.0.0'], next: ['core'] });
const warnings = bundledTargetWarnings(opts, ['core', 'bmm']);
assert(
warnings.some((w) => w.includes('bundled module')),
'bundledTargetWarnings: warns bundled pin',
);
assert(warnings.length === 2, 'bundledTargetWarnings: both pin and next warned');
}
// ─────────────────────────────────────────────────────────────────────────
// channel-resolver.js :: parseGitHubRepo
// ─────────────────────────────────────────────────────────────────────────
section('channel-resolver :: parseGitHubRepo');
{
const r = parseGitHubRepo('https://github.com/bmad-code-org/BMAD-METHOD');
assert(r && r.owner === 'bmad-code-org' && r.repo === 'BMAD-METHOD', 'https URL basic');
}
{
const r = parseGitHubRepo('https://github.com/bmad-code-org/BMAD-METHOD.git');
assert(r && r.repo === 'BMAD-METHOD', '.git suffix stripped');
}
{
const r = parseGitHubRepo('https://github.com/bmad-code-org/BMAD-METHOD/');
assert(r && r.repo === 'BMAD-METHOD', 'trailing slash stripped');
}
{
const r = parseGitHubRepo('https://github.com/org/repo/tree/main/subdir');
assert(r && r.owner === 'org' && r.repo === 'repo', 'deep path yields owner/repo');
}
{
const r = parseGitHubRepo('git@github.com:org/repo.git');
assert(r && r.owner === 'org' && r.repo === 'repo', 'SSH URL parsed');
}
assert(parseGitHubRepo('https://gitlab.com/foo/bar') === null, 'non-github URL returns null');
assert(parseGitHubRepo('') === null, 'empty string returns null');
assert(parseGitHubRepo(null) === null, 'null input returns null');
assert(parseGitHubRepo(123) === null, 'non-string input returns null');
// ─────────────────────────────────────────────────────────────────────────
// channel-resolver.js :: normalizeStableTag
// ─────────────────────────────────────────────────────────────────────────
section('channel-resolver :: normalizeStableTag');
assertEqual(normalizeStableTag('v1.2.3'), '1.2.3', 'strips leading v');
assertEqual(normalizeStableTag('1.2.3'), '1.2.3', 'bare semver accepted');
assertEqual(normalizeStableTag('v1.2.3-alpha.1'), null, 'prerelease -alpha excluded');
assertEqual(normalizeStableTag('v1.2.3-beta'), null, 'prerelease -beta excluded');
assertEqual(normalizeStableTag('v1.2.3-rc.1'), null, 'prerelease -rc excluded');
assertEqual(normalizeStableTag('not-a-version'), null, 'invalid string returns null');
assertEqual(normalizeStableTag('v1.2'), null, 'incomplete semver returns null');
assertEqual(normalizeStableTag(null), null, 'null returns null');
assertEqual(normalizeStableTag(123), null, 'non-string returns null');
// ─────────────────────────────────────────────────────────────────────────
// channel-resolver.js :: classifyUpgrade
// ─────────────────────────────────────────────────────────────────────────
section('channel-resolver :: classifyUpgrade');
assertEqual(classifyUpgrade('v1.2.3', 'v1.2.3'), 'none', 'equal versions → none');
assertEqual(classifyUpgrade('v1.2.3', 'v1.2.2'), 'none', 'downgrade → none');
assertEqual(classifyUpgrade('v1.2.3', 'v1.2.4'), 'patch', 'patch bump');
assertEqual(classifyUpgrade('v1.2.3', 'v1.3.0'), 'minor', 'minor bump');
assertEqual(classifyUpgrade('v1.2.3', 'v2.0.0'), 'major', 'major bump');
assertEqual(classifyUpgrade('1.2.3', '1.2.4'), 'patch', 'unprefixed versions work');
assertEqual(classifyUpgrade('main', 'v1.2.3'), 'unknown', 'non-semver current → unknown');
assertEqual(classifyUpgrade('v1.2.3', 'main'), 'unknown', 'non-semver next → unknown');
assertEqual(classifyUpgrade('', ''), 'unknown', 'both empty → unknown');
// ─────────────────────────────────────────────────────────────────────────
// channel-resolver.js :: releaseNotesUrl
// ─────────────────────────────────────────────────────────────────────────
section('channel-resolver :: releaseNotesUrl');
assertEqual(
releaseNotesUrl('https://github.com/bmad-code-org/BMAD-METHOD', 'v1.2.3'),
'https://github.com/bmad-code-org/BMAD-METHOD/releases/tag/v1.2.3',
'builds standard release URL',
);
assertEqual(releaseNotesUrl('https://gitlab.com/foo/bar', 'v1.0.0'), null, 'non-github repo → null');
assertEqual(releaseNotesUrl('https://github.com/foo/bar', null), null, 'null tag → null');
assertEqual(releaseNotesUrl('', 'v1.0.0'), null, 'empty URL → null');
// ─────────────────────────────────────────────────────────────────────────
// Summary
// ─────────────────────────────────────────────────────────────────────────
console.log('');
console.log(`${colors.cyan}========================================`);
console.log('Test Results:');
console.log(` Passed: ${colors.green}${passed}${colors.reset}`);
console.log(` Failed: ${colors.red}${failed}${colors.reset}`);
console.log(`========================================${colors.reset}\n`);
if (failed === 0) {
console.log(`${colors.green}✨ All channel resolution tests passed!${colors.reset}\n`);
process.exit(0);
} else {
console.log(`${colors.red}❌ Some channel resolution tests failed${colors.reset}\n`);
process.exit(1);
}
}
try {
runTests();
} catch (error) {
console.error(`${colors.red}Test runner failed:${colors.reset}`, error.message);
console.error(error.stack);
process.exit(1);
}

View File

@ -24,6 +24,19 @@ module.exports = {
['--output-folder <path>', 'Output folder path relative to project root (default: _bmad-output)'],
['--custom-source <sources>', 'Comma-separated Git URLs or local paths to install custom modules from'],
['-y, --yes', 'Accept all defaults and skip prompts where possible'],
[
'--channel <channel>',
'Apply channel (stable|next) to all external modules being installed. --all-stable and --all-next are aliases.',
],
['--all-stable', 'Alias for --channel=stable. Resolves externals to the highest stable release tag.'],
['--all-next', 'Alias for --channel=next. Resolves externals to main HEAD.'],
['--next <code>', 'Install module <code> from main HEAD (next channel). Repeatable.', (value, prev) => [...(prev || []), value], []],
[
'--pin <spec>',
'Pin module to a specific tag: --pin CODE=TAG (e.g. --pin bmb=v1.7.0). Repeatable.',
(value, prev) => [...(prev || []), value],
[],
],
],
action: async (options) => {
try {

View File

@ -3,7 +3,7 @@
* User input comes from either UI answers or headless CLI flags.
*/
class Config {
constructor({ directory, modules, ides, skipPrompts, verbose, actionType, coreConfig, moduleConfigs, quickUpdate }) {
constructor({ directory, modules, ides, skipPrompts, verbose, actionType, coreConfig, moduleConfigs, quickUpdate, channelOptions }) {
this.directory = directory;
this.modules = Object.freeze([...modules]);
this.ides = Object.freeze([...ides]);
@ -13,6 +13,8 @@ class Config {
this.coreConfig = coreConfig;
this.moduleConfigs = moduleConfigs;
this._quickUpdate = quickUpdate;
// channelOptions carry a Map + Set; don't deep-freeze.
this.channelOptions = channelOptions || null;
Object.freeze(this);
}
@ -37,6 +39,7 @@ class Config {
coreConfig: userInput.coreConfig || {},
moduleConfigs: userInput.moduleConfigs || null,
quickUpdate: userInput._quickUpdate || false,
channelOptions: userInput.channelOptions || null,
});
}

View File

@ -11,6 +11,7 @@ const prompts = require('../prompts');
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
const { InstallPaths } = require('./install-paths');
const { ExternalModuleManager } = require('../modules/external-manager');
const { resolveModuleVersion } = require('../modules/version-resolver');
const { ExistingInstall } = require('./existing-install');
@ -24,44 +25,6 @@ class Installer {
this.bmadFolderName = BMAD_FOLDER_NAME;
}
/**
* Read the module version from .claude-plugin/marketplace.json
* Walks up from sourcePath looking for .claude-plugin/marketplace.json
* @param {string} sourcePath - Module source directory
* @returns {string} Version string or empty string
*/
async _getMarketplaceVersion(sourcePath) {
let dir = sourcePath;
for (let i = 0; i < 5; i++) {
const marketplacePath = path.join(dir, '.claude-plugin', 'marketplace.json');
if (await fs.pathExists(marketplacePath)) {
try {
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
return this._extractMarketplaceVersion(data);
} catch {
return '';
}
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return '';
}
/**
* Extract the highest version from marketplace.json plugins array
*/
_extractMarketplaceVersion(data) {
const plugins = data?.plugins;
if (!Array.isArray(plugins) || plugins.length === 0) return '';
let best = '';
for (const p of plugins) {
if (p.version && (!best || p.version > best)) best = p.version;
}
return best;
}
/**
* Main installation method
* @param {Object} config - Installation configuration
@ -638,19 +601,40 @@ class Installer {
moduleConfig: moduleConfig,
installer: this,
silent: true,
channelOptions: config.channelOptions,
},
);
// Get display name from source module.yaml; version from resolution cache or marketplace.json
const sourcePath = await officialModules.findModuleSource(moduleName, { silent: true });
// Get display name from source module.yaml and resolve the freshest version metadata we can find locally.
const sourcePath = await officialModules.findModuleSource(moduleName, {
silent: true,
channelOptions: config.channelOptions,
});
const moduleInfo = sourcePath ? await officialModules.getModuleInfo(sourcePath, moduleName, '') : null;
const displayName = moduleInfo?.name || moduleName;
// Prefer version from resolution cache (accurate for custom/local modules),
// fall back to marketplace.json walk-up for official modules
const externalResolution = officialModules.externalModuleManager.getResolution(moduleName);
let communityResolution = null;
if (!externalResolution) {
const { CommunityModuleManager } = require('../modules/community-manager');
communityResolution = new CommunityModuleManager().getResolution(moduleName);
}
const resolution = externalResolution || communityResolution;
const cachedResolution = CustomModuleManager._resolutionCache.get(moduleName);
const version = cachedResolution?.version || (sourcePath ? await this._getMarketplaceVersion(sourcePath) : '');
addResult(displayName, 'ok', '', { moduleCode: moduleName, newVersion: version });
const versionInfo = await resolveModuleVersion(moduleName, {
moduleSourcePath: sourcePath,
fallbackVersion: resolution?.version || cachedResolution?.version,
marketplacePluginNames: cachedResolution?.pluginName ? [cachedResolution.pluginName] : [],
});
// Prefer the git tag recorded by the resolution (e.g. "v1.7.0") over
// the on-disk package.json (which may be ahead of the released tag).
const version = resolution?.version || versionInfo.version || '';
addResult(displayName, 'ok', '', {
moduleCode: moduleName,
newVersion: version,
newChannel: resolution?.channel || null,
newSha: resolution?.sha || null,
});
}
}
@ -1125,12 +1109,30 @@ class Installer {
let detail = '';
if (r.moduleCode && r.newVersion) {
const oldVersion = preVersions.get(r.moduleCode);
if (oldVersion && oldVersion === r.newVersion) {
detail = ` (v${r.newVersion}, no change)`;
// Format a version label for display:
// "main" → "main @ <short-sha>" (next channel shows what SHA landed)
// "v1.7.0" or "1.7.0" → "v1.7.0" (prefix 'v' when missing)
// anything else (legacy strings) → as-is
const fmt = (v, sha) => {
if (typeof v !== 'string' || !v) return '';
if (v === 'main' || v === 'HEAD') return sha ? `main @ ${sha.slice(0, 7)}` : 'main';
if (/^v?\d+\.\d+\.\d+/.test(v)) return v.startsWith('v') ? v : `v${v}`;
return v;
};
const newV = fmt(r.newVersion, r.newSha);
// 'main'/'HEAD' strings only identify the channel, not the commit, so
// we can't assert "no change" without comparing SHAs — and preVersions
// doesn't carry the old SHA. Render these as a refresh instead of a
// false-negative "no change".
const isMainLike = oldVersion === 'main' || oldVersion === 'HEAD';
if (oldVersion && oldVersion === r.newVersion && !isMainLike) {
detail = ` (${newV}, no change)`;
} else if (oldVersion && isMainLike) {
detail = ` (${newV}, refreshed)`;
} else if (oldVersion) {
detail = ` (v${oldVersion} → v${r.newVersion})`;
detail = ` (${fmt(oldVersion, r.newSha)}${newV})`;
} else {
detail = ` (v${r.newVersion}, installed)`;
detail = ` (${newV}, installed)`;
}
} else if (r.detail) {
detail = ` (${r.detail})`;
@ -1250,9 +1252,59 @@ class Installer {
await prompts.log.warn(`Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`);
}
// Build channel options from the existing manifest FIRST so the config
// collector below (which triggers external-module clones via
// findModuleSource) knows each module's recorded channel and doesn't
// silently redecide it. Without this, modules previously on 'next' or
// 'pinned' would trigger a stable-channel tag lookup at config-collection
// time, burning GitHub API quota and potentially failing.
const manifestData = await this.manifest.read(bmadDir);
const channelOptions = { global: null, nextSet: new Set(), pins: new Map(), warnings: [] };
if (manifestData?.modulesDetailed) {
const { fetchStableTags, classifyUpgrade, parseGitHubRepo } = require('../modules/channel-resolver');
for (const entry of manifestData.modulesDetailed) {
if (!entry?.name || !entry?.channel) continue;
if (entry.channel === 'pinned' && entry.version) {
channelOptions.pins.set(entry.name, entry.version);
continue;
}
if (entry.channel === 'next') {
channelOptions.nextSet.add(entry.name);
continue;
}
// Stable: classify the available upgrade. Patches and minors fall
// through (stable default picks up the top tag). A major upgrade
// requires opt-in, so under quick-update's non-interactive semantics
// we pin to the current version to prevent a silent breaking jump.
if (entry.channel === 'stable' && entry.version && entry.repoUrl) {
const parsed = parseGitHubRepo(entry.repoUrl);
if (!parsed) continue;
try {
const tags = await fetchStableTags(parsed.owner, parsed.repo);
if (tags.length === 0) continue;
const topTag = tags[0].tag;
const cls = classifyUpgrade(entry.version, topTag);
if (cls === 'major') {
channelOptions.pins.set(entry.name, entry.version);
await prompts.log.warn(
`${entry.name} ${entry.version}${topTag} is a new major release; staying on ${entry.version}. ` +
`Run \`bmad install\` (Modify) with \`--pin ${entry.name}=${topTag}\` to accept.`,
);
}
} catch (error) {
// Tag lookup failed (offline, rate-limited). Stay on the current
// version rather than guessing — the existing cache is already
// at that ref, so re-using it keeps the install stable.
channelOptions.pins.set(entry.name, entry.version);
await prompts.log.warn(`Could not check ${entry.name} for updates (${error.message}); staying on ${entry.version}.`);
}
}
}
}
// Load existing configs and collect new fields (if any)
await prompts.log.info('Checking for new configuration options...');
const quickModules = new OfficialModules();
const quickModules = new OfficialModules({ channelOptions });
await quickModules.loadExistingConfig(projectDir);
let promptedForNewFields = false;
@ -1291,6 +1343,7 @@ class Installer {
_quickUpdate: true,
_preserveModules: skippedModules,
_existingModules: installedModules,
channelOptions,
};
await this.install(installConfig);

View File

@ -2,7 +2,7 @@ const path = require('node:path');
const fs = require('../fs-native');
const yaml = require('yaml');
const crypto = require('node:crypto');
const { getModulePath } = require('../project-root');
const { resolveInstalledModuleYaml } = require('../project-root');
const prompts = require('../prompts');
// Load package.json for version info
@ -244,8 +244,17 @@ class ManifestGenerator {
const debug = process.env.BMAD_DEBUG_MANIFEST === 'true';
for (const moduleName of this.updatedModules) {
const moduleYamlPath = path.join(getModulePath(moduleName), 'module.yaml');
if (!(await fs.pathExists(moduleYamlPath))) continue;
const moduleYamlPath = await resolveInstalledModuleYaml(moduleName);
if (!moduleYamlPath) {
// External modules live in ~/.bmad/cache/external-modules, not src/modules.
// Warn rather than silently skip so missing agent rosters don't vanish
// from config.toml without notice.
console.warn(
`[warn] collectAgentsFromModuleYaml: could not locate module.yaml for '${moduleName}'. ` +
`Agents declared by this module will not be written to config.toml.`,
);
continue;
}
let moduleDef;
try {
@ -271,7 +280,9 @@ class ManifestGenerator {
}
if (debug) {
console.log(`[DEBUG] collectAgentsFromModuleYaml: ${moduleName} contributed ${moduleDef.agents.length} agents`);
console.log(
`[DEBUG] collectAgentsFromModuleYaml: ${moduleName} contributed ${moduleDef.agents.length} agents from ${moduleYamlPath}`,
);
}
}
@ -338,7 +349,22 @@ class ManifestGenerator {
npmPackage: versionInfo.npmPackage,
repoUrl: versionInfo.repoUrl,
};
if (versionInfo.localPath) moduleEntry.localPath = versionInfo.localPath;
// Preserve channel/sha from the resolution (external/community/custom)
// or from the existing entry if this is a no-change rewrite.
const channel = versionInfo.channel ?? existing?.channel;
const sha = versionInfo.sha ?? existing?.sha;
if (channel) moduleEntry.channel = channel;
if (sha) moduleEntry.sha = sha;
if (versionInfo.localPath || existing?.localPath) {
moduleEntry.localPath = versionInfo.localPath || existing.localPath;
}
if (versionInfo.rawSource || existing?.rawSource) {
moduleEntry.rawSource = versionInfo.rawSource || existing.rawSource;
}
const regTag = versionInfo.registryApprovedTag ?? existing?.registryApprovedTag;
const regSha = versionInfo.registryApprovedSha ?? existing?.registryApprovedSha;
if (regTag) moduleEntry.registryApprovedTag = regTag;
if (regSha) moduleEntry.registryApprovedSha = regSha;
updatedModules.push(moduleEntry);
}
@ -410,8 +436,14 @@ class ManifestGenerator {
// team config, so the operator should notice.
const scopeByModuleKey = {};
for (const moduleName of this.updatedModules) {
const moduleYamlPath = path.join(getModulePath(moduleName), 'module.yaml');
if (!(await fs.pathExists(moduleYamlPath))) continue;
const moduleYamlPath = await resolveInstalledModuleYaml(moduleName);
if (!moduleYamlPath) {
console.warn(
`[warn] writeCentralConfig: could not locate module.yaml for '${moduleName}'. ` +
`Answers from this module will default to team scope — user-scoped keys may mis-file into config.toml.`,
);
continue;
}
try {
const parsed = yaml.parse(await fs.readFile(moduleYamlPath, 'utf8'));
if (!parsed || typeof parsed !== 'object') continue;

View File

@ -1,7 +1,7 @@
const path = require('node:path');
const fs = require('../fs-native');
const crypto = require('node:crypto');
const { getProjectRoot } = require('../project-root');
const { resolveModuleVersion } = require('../modules/version-resolver');
const prompts = require('../prompts');
class Manifest {
@ -180,7 +180,12 @@ class Manifest {
npmPackage: options.npmPackage || null,
repoUrl: options.repoUrl || null,
};
if (options.channel) entry.channel = options.channel;
if (options.sha) entry.sha = options.sha;
if (options.localPath) entry.localPath = options.localPath;
if (options.rawSource) entry.rawSource = options.rawSource;
if (options.registryApprovedTag) entry.registryApprovedTag = options.registryApprovedTag;
if (options.registryApprovedSha) entry.registryApprovedSha = options.registryApprovedSha;
manifest.modules.push(entry);
} else {
// Module exists, update its version info
@ -192,6 +197,11 @@ class Manifest {
npmPackage: options.npmPackage === undefined ? existing.npmPackage : options.npmPackage,
repoUrl: options.repoUrl === undefined ? existing.repoUrl : options.repoUrl,
localPath: options.localPath === undefined ? existing.localPath : options.localPath,
channel: options.channel === undefined ? existing.channel : options.channel,
sha: options.sha === undefined ? existing.sha : options.sha,
rawSource: options.rawSource === undefined ? existing.rawSource : options.rawSource,
registryApprovedTag: options.registryApprovedTag === undefined ? existing.registryApprovedTag : options.registryApprovedTag,
registryApprovedSha: options.registryApprovedSha === undefined ? existing.registryApprovedSha : options.registryApprovedSha,
lastUpdated: new Date().toISOString(),
};
}
@ -258,13 +268,11 @@ class Manifest {
* @returns {Object} Version info object with version, source, npmPackage, repoUrl
*/
async getModuleVersionInfo(moduleName, bmadDir, moduleSourcePath = null) {
const yaml = require('yaml');
// Resolve source type first, then read version with the correct path context
if (['core', 'bmm'].includes(moduleName)) {
const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
return {
version,
version: versionInfo.version,
source: 'built-in',
npmPackage: null,
repoUrl: null,
@ -277,13 +285,17 @@ class Manifest {
const moduleInfo = await extMgr.getModuleByCode(moduleName);
if (moduleInfo) {
// External module: use moduleSourcePath if provided, otherwise fall back to cache
const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
const externalResolution = extMgr.getResolution(moduleName);
const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
return {
version,
// Git tag recorded during install trumps the on-disk package.json
// version, so the manifest carries "v1.7.0" instead of "1.7.0".
version: externalResolution?.version || versionInfo.version,
source: 'external',
npmPackage: moduleInfo.npmPackage || null,
repoUrl: moduleInfo.url || null,
channel: externalResolution?.channel || null,
sha: externalResolution?.sha || null,
};
}
@ -292,12 +304,20 @@ class Manifest {
const communityMgr = new CommunityModuleManager();
const communityInfo = await communityMgr.getModuleByCode(moduleName);
if (communityInfo) {
const communityVersion = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
const communityResolution = communityMgr.getResolution(moduleName);
const versionInfo = await resolveModuleVersion(moduleName, {
moduleSourcePath,
fallbackVersion: communityInfo.version,
});
return {
version: communityVersion || communityInfo.version,
version: communityResolution?.version || versionInfo.version || communityInfo.version,
source: 'community',
npmPackage: communityInfo.npmPackage || null,
repoUrl: communityInfo.url || null,
channel: communityResolution?.channel || null,
sha: communityResolution?.sha || null,
registryApprovedTag: communityResolution?.registryApprovedTag || null,
registryApprovedSha: communityResolution?.registryApprovedSha || null,
};
}
@ -307,75 +327,35 @@ class Manifest {
const resolved = customMgr.getResolution(moduleName);
const customSource = await customMgr.findModuleSourceByCode(moduleName, { bmadDir });
if (customSource || resolved) {
const customVersion = resolved?.version || (await this._readMarketplaceVersion(moduleName, moduleSourcePath));
const versionInfo = await resolveModuleVersion(moduleName, {
moduleSourcePath: moduleSourcePath || customSource,
fallbackVersion: resolved?.version,
marketplacePluginNames: resolved?.pluginName ? [resolved.pluginName] : [],
});
const hasGitClone = !!resolved?.repoUrl;
return {
version: customVersion,
// Prefer the git ref we actually cloned over the package.json version.
version: resolved?.cloneRef || (hasGitClone ? 'main' : versionInfo.version),
source: 'custom',
npmPackage: null,
repoUrl: resolved?.repoUrl || null,
localPath: resolved?.localPath || null,
channel: hasGitClone ? (resolved?.cloneRef ? 'pinned' : 'next') : null,
sha: resolved?.cloneSha || null,
rawSource: resolved?.rawInput || null,
};
}
// Unknown module
const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
return {
version,
version: versionInfo.version,
source: 'unknown',
npmPackage: null,
repoUrl: null,
};
}
/**
* Read version from .claude-plugin/marketplace.json for a module
* @param {string} moduleName - Module code
* @returns {string|null} Version or null
*/
async _readMarketplaceVersion(moduleName, moduleSourcePath = null) {
const os = require('node:os');
let marketplacePath;
if (['core', 'bmm'].includes(moduleName)) {
marketplacePath = path.join(getProjectRoot(), '.claude-plugin', 'marketplace.json');
} else if (moduleSourcePath) {
// Walk up from source path to find marketplace.json
let dir = moduleSourcePath;
for (let i = 0; i < 5; i++) {
const candidate = path.join(dir, '.claude-plugin', 'marketplace.json');
if (await fs.pathExists(candidate)) {
marketplacePath = candidate;
break;
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
// Fallback to external module cache
if (!marketplacePath) {
const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleName);
marketplacePath = path.join(cacheDir, '.claude-plugin', 'marketplace.json');
}
try {
if (await fs.pathExists(marketplacePath)) {
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
const plugins = data?.plugins;
if (!Array.isArray(plugins) || plugins.length === 0) return null;
let best = null;
for (const p of plugins) {
if (p.version && (!best || p.version > best)) best = p.version;
}
return best;
}
} catch {
// ignore
}
return null;
}
/**
* Fetch latest version from npm for a package
* @param {string} packageName - npm package name
@ -424,6 +404,7 @@ class Manifest {
* @returns {Array} Array of update info objects
*/
async checkForUpdates(bmadDir) {
const semver = require('semver');
const modules = await this.getAllModuleVersions(bmadDir);
const updates = [];
@ -437,7 +418,10 @@ class Manifest {
continue;
}
if (module.version !== latestVersion) {
const installedVersion = semver.valid(module.version) || semver.valid(semver.coerce(module.version || ''));
const availableVersion = semver.valid(latestVersion) || semver.valid(semver.coerce(latestVersion));
if (installedVersion && availableVersion && semver.gt(availableVersion, installedVersion)) {
updates.push({
name: module.name,
installedVersion: module.version,

View File

@ -0,0 +1,203 @@
/**
* Channel plan: the per-module resolution decision applied at install time.
*
* A "plan entry" for a module is:
* { channel: 'stable'|'next'|'pinned', pin?: string }
*
* We build the plan from:
* 1. CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG)
* 2. Interactive answers (the "all stable?" gate + per-module picker)
* 3. Registry defaults (default_channel from registry-fallback.yaml / official.yaml)
* 4. Hardcoded fallback 'stable'
*
* Precedence: --pin > --next=CODE > --channel (global) > registry default > 'stable'.
*
* This module is pure. No prompts, no git, no filesystem.
*/
const VALID_CHANNELS = new Set(['stable', 'next']);
/**
* Parse raw commander options into a structured channel options object.
*
* @param {Object} options - raw command-line options
* @returns {{
* global: 'stable'|'next'|null,
* nextSet: Set<string>,
* pins: Map<string, string>,
* warnings: string[]
* }}
*/
function parseChannelOptions(options = {}) {
const warnings = [];
// Global channel from --channel / --all-stable / --all-next.
let global = null;
const aliases = [];
if (options.channel) aliases.push({ flag: '--channel', value: normalizeChannel(options.channel, warnings, '--channel') });
if (options.allStable) aliases.push({ flag: '--all-stable', value: 'stable' });
if (options.allNext) aliases.push({ flag: '--all-next', value: 'next' });
const distinct = new Set(aliases.map((a) => a.value).filter(Boolean));
if (distinct.size > 1) {
warnings.push(
`Conflicting channel flags: ${aliases
.filter((a) => a.value)
.map((a) => a.flag + '=' + a.value)
.join(', ')}. Using first: ${aliases.find((a) => a.value).flag}.`,
);
}
const firstValid = aliases.find((a) => a.value);
if (firstValid) global = firstValid.value;
// --next=CODE (repeatable)
const nextSet = new Set();
for (const code of options.next || []) {
const trimmed = String(code).trim();
if (!trimmed) continue;
nextSet.add(trimmed);
}
// --pin CODE=TAG (repeatable)
const pins = new Map();
for (const spec of options.pin || []) {
const parsed = parsePinSpec(spec);
if (!parsed) {
warnings.push(`Ignoring malformed --pin value '${spec}'. Expected CODE=TAG.`);
continue;
}
if (pins.has(parsed.code)) {
warnings.push(`--pin specified multiple times for '${parsed.code}'. Using last: ${parsed.tag}.`);
}
pins.set(parsed.code, parsed.tag);
}
// --yes auto-confirms the community-module curator-bypass prompt so
// headless installs with --next=/--pin for a community module don't hang.
const acceptBypass = options.yes === true || options.acceptBypass === true;
return { global, nextSet, pins, warnings, acceptBypass };
}
function normalizeChannel(raw, warnings, flagName) {
if (typeof raw !== 'string') return null;
const lower = raw.trim().toLowerCase();
if (VALID_CHANNELS.has(lower)) return lower;
warnings.push(`Ignoring invalid ${flagName} value '${raw}'. Expected one of: stable, next.`);
return null;
}
function parsePinSpec(spec) {
if (typeof spec !== 'string') return null;
const idx = spec.indexOf('=');
if (idx <= 0 || idx === spec.length - 1) return null;
const code = spec.slice(0, idx).trim();
const tag = spec.slice(idx + 1).trim();
if (!code || !tag) return null;
return { code, tag };
}
/**
* Build a per-module plan entry, applying precedence.
*
* @param {Object} args
* @param {string} args.code
* @param {Object} args.channelOptions - from parseChannelOptions
* @param {string} [args.registryDefault] - module's default_channel, if any
* @returns {{channel: 'stable'|'next'|'pinned', pin?: string, source: string}}
* source describes where the decision came from, for logging / debugging.
*/
function decideChannelForModule({ code, channelOptions, registryDefault }) {
const { global, nextSet, pins } = channelOptions || { nextSet: new Set(), pins: new Map() };
if (pins && pins.has(code)) {
return { channel: 'pinned', pin: pins.get(code), source: 'flag:--pin' };
}
if (nextSet && nextSet.has(code)) {
return { channel: 'next', source: 'flag:--next' };
}
if (global) {
return { channel: global, source: 'flag:--channel' };
}
if (registryDefault && VALID_CHANNELS.has(registryDefault)) {
return { channel: registryDefault, source: 'registry' };
}
return { channel: 'stable', source: 'default' };
}
/**
* Build a full channel plan map for a set of modules.
*
* @param {Object} args
* @param {Array<{code: string, defaultChannel?: string, builtIn?: boolean}>} args.modules
* Only the modules that need a channel entry; callers should filter out
* bundled modules (core/bmm) before calling.
* @param {Object} args.channelOptions - from parseChannelOptions
* @returns {Map<string, {channel: string, pin?: string, source: string}>}
*/
function buildPlan({ modules, channelOptions }) {
const plan = new Map();
for (const mod of modules || []) {
plan.set(
mod.code,
decideChannelForModule({
code: mod.code,
channelOptions,
registryDefault: mod.defaultChannel,
}),
);
}
return plan;
}
/**
* Report any --pin CODE=TAG entries that don't correspond to a selected module.
* These get warned about but don't abort the install.
*/
function orphanPinWarnings(channelOptions, selectedCodes) {
const warnings = [];
const selected = new Set(selectedCodes || []);
for (const code of channelOptions?.pins?.keys() || []) {
if (!selected.has(code)) {
warnings.push(`--pin for '${code}' has no effect (module not selected).`);
}
}
for (const code of channelOptions?.nextSet || []) {
if (!selected.has(code)) {
warnings.push(`--next for '${code}' has no effect (module not selected).`);
}
}
return warnings;
}
/**
* Warn when --pin / --next targets a bundled module (core, bmm). Those are
* shipped inside the installer binary there's no git clone to override, so
* the flag has no effect. Users who actually want a prerelease core/bmm
* should use `npx bmad-method@next install`.
*/
function bundledTargetWarnings(channelOptions, bundledCodes) {
const warnings = [];
const bundled = new Set(bundledCodes || []);
const hint = '(bundled module; use `npx bmad-method@next install` for a prerelease)';
for (const code of channelOptions?.pins?.keys() || []) {
if (bundled.has(code)) {
warnings.push(`--pin for '${code}' has no effect ${hint}.`);
}
}
for (const code of channelOptions?.nextSet || []) {
if (bundled.has(code)) {
warnings.push(`--next for '${code}' has no effect ${hint}.`);
}
}
return warnings;
}
module.exports = {
parseChannelOptions,
decideChannelForModule,
buildPlan,
orphanPinWarnings,
bundledTargetWarnings,
parsePinSpec,
};

View File

@ -0,0 +1,241 @@
const https = require('node:https');
const semver = require('semver');
/**
* Channel resolver for external and community modules.
*
* A "channel" is the resolution strategy that decides which ref of a module
* to clone when no explicit version is supplied:
* - stable: highest pure-semver git tag (excludes -alpha/-beta/-rc)
* - next: main branch HEAD
* - pinned: an explicit user-supplied tag
*
* This module is pure (no prompts, no git, no filesystem). It only talks to
* the GitHub tags API and performs semver math. Clone logic lives in the
* module managers that call resolveChannel().
*/
const GITHUB_API_BASE = 'https://api.github.com';
const DEFAULT_TIMEOUT_MS = 10_000;
const USER_AGENT = 'bmad-method-installer';
// Per-process cache: { 'owner/repo' => string[] sorted desc } of pure-semver tags.
const tagCache = new Map();
/**
* Parse a GitHub repo URL into { owner, repo }. Returns null if the URL is
* not a GitHub URL the resolver can handle.
*/
function parseGitHubRepo(url) {
if (!url || typeof url !== 'string') return null;
const trimmed = url
.trim()
.replace(/\.git$/, '')
.replace(/\/$/, '');
// https://github.com/owner/repo
const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)(?:\/.*)?$/i);
if (httpsMatch) return { owner: httpsMatch[1], repo: httpsMatch[2] };
// git@github.com:owner/repo
const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/]+)$/i);
if (sshMatch) return { owner: sshMatch[1], repo: sshMatch[2] };
return null;
}
function fetchJson(url, { timeout = DEFAULT_TIMEOUT_MS } = {}) {
const headers = {
'User-Agent': USER_AGENT,
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
};
if (process.env.GITHUB_TOKEN) {
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
}
return new Promise((resolve, reject) => {
const req = https.get(url, { headers, timeout }, (res) => {
let body = '';
res.on('data', (chunk) => (body += chunk));
res.on('end', () => {
if (res.statusCode < 200 || res.statusCode >= 300) {
const err = new Error(`GitHub API ${res.statusCode} for ${url}: ${body.slice(0, 200)}`);
err.statusCode = res.statusCode;
return reject(err);
}
try {
resolve(JSON.parse(body));
} catch (error) {
reject(new Error(`Failed to parse GitHub response: ${error.message}`));
}
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error(`GitHub API request timed out: ${url}`));
});
});
}
/**
* Strip a leading 'v' and return a valid semver string, or null if the tag
* is not valid semver or is a prerelease (contains -alpha/-beta/-rc/etc.).
*/
function normalizeStableTag(tagName) {
if (typeof tagName !== 'string') return null;
const stripped = tagName.startsWith('v') ? tagName.slice(1) : tagName;
const valid = semver.valid(stripped);
if (!valid) return null;
// Exclude prereleases. semver.prerelease returns null for pure releases.
if (semver.prerelease(valid)) return null;
return valid;
}
/**
* Fetch pure-semver tags (highest first) from a GitHub repo.
* Cached per-process per owner/repo.
*
* @returns {Promise<Array<{tag: string, version: string}>>}
* tag is the original ref name (e.g. "v1.7.0"), version is the cleaned
* semver (e.g. "1.7.0").
*/
async function fetchStableTags(owner, repo, { timeout } = {}) {
const cacheKey = `${owner}/${repo}`;
if (tagCache.has(cacheKey)) return tagCache.get(cacheKey);
// GitHub returns up to 100 tags per page; one page is plenty for our modules.
const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/tags?per_page=100`;
const raw = await fetchJson(url, { timeout });
if (!Array.isArray(raw)) {
throw new TypeError(`Unexpected response from ${url}`);
}
const stable = [];
for (const entry of raw) {
const version = normalizeStableTag(entry?.name);
if (version) stable.push({ tag: entry.name, version });
}
stable.sort((a, b) => semver.rcompare(a.version, b.version));
tagCache.set(cacheKey, stable);
return stable;
}
/**
* Resolve a channel plan for a single module into a git-clonable ref.
*
* @param {Object} args
* @param {'stable'|'next'|'pinned'} args.channel
* @param {string} [args.pin] - Required when channel === 'pinned'
* @param {string} args.repoUrl - Module's git URL (for tag lookup)
* @returns {Promise<{channel, ref, version}>} where
* ref: the git ref to pass to `git clone --branch`, or null for HEAD (next)
* version: the resolved version string (tag name for stable/pinned, 'main' for next)
*
* Throws on:
* - pinned without a pin value
* - stable with no GitHub repo parseable from the URL (pass through to caller to fall back)
*
* Falls back to next-channel semantics and sets resolvedFallback=true when
* stable resolution turns up no tags.
*/
async function resolveChannel({ channel, pin, repoUrl, timeout }) {
if (channel === 'pinned') {
if (!pin) throw new Error('resolveChannel: pinned channel requires a pin value');
return { channel: 'pinned', ref: pin, version: pin, resolvedFallback: false };
}
if (channel === 'next') {
return { channel: 'next', ref: null, version: 'main', resolvedFallback: false };
}
if (channel === 'stable') {
const parsed = parseGitHubRepo(repoUrl);
if (!parsed) {
// No GitHub URL — caller must handle by falling back to next.
return { channel: 'next', ref: null, version: 'main', resolvedFallback: true, reason: 'not-a-github-url' };
}
try {
const tags = await fetchStableTags(parsed.owner, parsed.repo, { timeout });
if (tags.length === 0) {
return { channel: 'next', ref: null, version: 'main', resolvedFallback: true, reason: 'no-stable-tags' };
}
const top = tags[0];
return { channel: 'stable', ref: top.tag, version: top.tag, resolvedFallback: false };
} catch (error) {
// Propagate the error; callers decide whether to fall back or abort.
error.message = `Failed to resolve stable channel for ${parsed.owner}/${parsed.repo}: ${error.message}`;
throw error;
}
}
throw new Error(`resolveChannel: unknown channel '${channel}'`);
}
/**
* Verify that a specific tag exists in a GitHub repo. Used to validate
* --pin values before the user sits through a long clone that then fails.
*/
async function tagExists(owner, repo, tagName, { timeout } = {}) {
const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/git/refs/tags/${encodeURIComponent(tagName)}`;
try {
await fetchJson(url, { timeout });
return true;
} catch (error) {
if (error.statusCode === 404) return false;
throw error;
}
}
/**
* Classify the semver delta between two versions.
* - 'none' same version (or downgrade; treated same)
* - 'patch' same major.minor, higher patch
* - 'minor' same major, higher minor
* - 'major' different major
* - 'unknown' either version is not valid semver; caller should treat as major
*/
function classifyUpgrade(currentVersion, newVersion) {
const current = semver.valid(semver.coerce(currentVersion));
const next = semver.valid(semver.coerce(newVersion));
if (!current || !next) return 'unknown';
if (semver.lte(next, current)) return 'none';
const diff = semver.diff(current, next);
if (diff === 'patch') return 'patch';
if (diff === 'minor' || diff === 'preminor') return 'minor';
if (diff === 'major' || diff === 'premajor') return 'major';
// prepatch, prerelease — treat conservatively as minor (prereleases shouldn't
// normally surface here since stable channel filters them out).
return 'minor';
}
/**
* Build the GitHub release notes URL for a resolved tag.
* Returns null if the repo URL isn't a GitHub URL.
*/
function releaseNotesUrl(repoUrl, tag) {
const parsed = parseGitHubRepo(repoUrl);
if (!parsed || !tag) return null;
return `https://github.com/${parsed.owner}/${parsed.repo}/releases/tag/${encodeURIComponent(tag)}`;
}
/**
* Test-only: clear the per-process tag cache.
*/
function _clearTagCache() {
tagCache.clear();
}
module.exports = {
parseGitHubRepo,
fetchStableTags,
resolveChannel,
tagExists,
classifyUpgrade,
releaseNotesUrl,
normalizeStableTag,
_clearTagCache,
};

View File

@ -4,6 +4,8 @@ const path = require('node:path');
const { execSync } = require('node:child_process');
const prompts = require('../prompts');
const { RegistryClient } = require('./registry-client');
const { decideChannelForModule } = require('./channel-plan');
const { parseGitHubRepo, tagExists } = require('./channel-resolver');
const MARKETPLACE_OWNER = 'bmad-code-org';
const MARKETPLACE_REPO = 'bmad-plugins-marketplace';
@ -15,13 +17,29 @@ const MARKETPLACE_REF = 'main';
* Returns empty results when the registry is unreachable.
* Community modules are pinned to approved SHA when set; uses HEAD otherwise.
*/
function quoteShellRef(ref) {
if (typeof ref !== 'string' || !/^[\w.\-+/]+$/.test(ref)) {
throw new Error(`Unsafe ref name: ${JSON.stringify(ref)}`);
}
return `"${ref}"`;
}
class CommunityModuleManager {
// moduleCode → { channel, version, sha, registryApprovedTag, registryApprovedSha, repoUrl, bypassedCurator }
// Shared across all instances; the manifest writer often uses a fresh instance.
static _resolutions = new Map();
constructor() {
this._client = new RegistryClient();
this._cachedIndex = null;
this._cachedCategories = null;
}
/** Get the most recent channel resolution for a community module. */
getResolution(moduleCode) {
return CommunityModuleManager._resolutions.get(moduleCode) || null;
}
// ─── Data Loading ──────────────────────────────────────────────────────────
/**
@ -196,12 +214,49 @@ class CommunityModuleManager {
return await prompts.spinner();
};
const sha = moduleInfo.approvedSha;
// ─── Resolve channel plan ──────────────────────────────────────────────
// Default community behavior (stable channel) honors the curator's
// approved SHA. --next=CODE and --pin CODE=TAG override the curator; we
// warn the user before bypassing the approved version.
const planEntry = decideChannelForModule({
code: moduleCode,
channelOptions: options.channelOptions,
registryDefault: 'stable',
});
const approvedSha = moduleInfo.approvedSha;
const approvedTag = moduleInfo.approvedTag;
let bypassedCurator = false;
if (planEntry.channel !== 'stable') {
bypassedCurator = true;
if (!silent) {
const approvedLabel = approvedTag || approvedSha || 'curator-approved version';
await prompts.log.warn(
`WARNING: Installing '${moduleCode}' from ${
planEntry.channel === 'pinned' ? `tag ${planEntry.pin}` : 'main HEAD'
} bypasses the curator-approved ${approvedLabel}. Proceed only if you trust this source.`,
);
if (!options.channelOptions?.acceptBypass) {
const proceed = await prompts.confirm({
message: `Continue installing '${moduleCode}' with curator bypass?`,
default: false,
});
if (!proceed) {
throw new Error(`Install of community module '${moduleCode}' cancelled by user.`);
}
}
}
}
let needsDependencyInstall = false;
let wasNewClone = false;
if (await fs.pathExists(moduleCacheDir)) {
// Already cloned - update to latest HEAD
// Already cloned — refresh to the correct ref for the resolved channel.
// A pinned install must not reset to origin/HEAD (it would silently drift
// to main on every re-install). Stable + approvedSha is handled below
// by the curator-SHA checkout logic.
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Checking ${moduleInfo.displayName}...`);
try {
@ -211,10 +266,24 @@ class CommunityModuleManager {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync('git reset --hard origin/HEAD', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
if (planEntry.channel === 'pinned') {
// Fetch the pin tag specifically and check it out.
execSync(`git fetch --depth 1 origin ${quoteShellRef(planEntry.pin)} --no-tags`, {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync('git checkout --quiet FETCH_HEAD', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
} else {
// stable (approvedSha path re-checks out below) and next: track main.
execSync('git reset --hard origin/HEAD', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
}
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
if (currentRef !== newRef) needsDependencyInstall = true;
fetchSpinner.stop(`Verified ${moduleInfo.displayName}`);
@ -231,10 +300,17 @@ class CommunityModuleManager {
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Fetching ${moduleInfo.displayName}...`);
try {
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
if (planEntry.channel === 'pinned') {
execSync(`git clone --depth 1 --branch ${quoteShellRef(planEntry.pin)} "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
} else {
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
}
fetchSpinner.stop(`Fetched ${moduleInfo.displayName}`);
needsDependencyInstall = true;
} catch (error) {
@ -243,18 +319,19 @@ class CommunityModuleManager {
}
}
// If pinned to a specific SHA, check out that exact commit.
// Refuse to install if the approved SHA cannot be reached - security requirement.
if (sha) {
// ─── Check out the resolved ref per channel ──────────────────────────
if (planEntry.channel === 'stable' && approvedSha) {
// Default path: pin to the curator-approved SHA. Refuse install if the SHA
// is unreachable (tag may have been deleted or rewritten) — security requirement.
const headSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
if (headSha !== sha) {
if (headSha !== approvedSha) {
try {
execSync(`git fetch --depth 1 origin ${sha}`, {
execSync(`git fetch --depth 1 origin ${quoteShellRef(approvedSha)}`, {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync(`git checkout ${sha}`, {
execSync(`git checkout ${quoteShellRef(approvedSha)}`, {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
@ -262,12 +339,37 @@ class CommunityModuleManager {
} catch {
await fs.remove(moduleCacheDir);
throw new Error(
`Community module '${moduleCode}' could not be pinned to its approved commit (${sha}). ` +
`Installation refused for security. The module registry entry may need updating.`,
`Community module '${moduleCode}' could not be pinned to its approved commit (${approvedSha}). ` +
`Installation refused for security. The module registry entry may need updating, ` +
`or use --next=${moduleCode} / --pin ${moduleCode}=<tag> to explicitly bypass.`,
);
}
}
} else if (planEntry.channel === 'stable' && !approvedSha) {
// Registry data gap: tag or SHA missing. Warn but proceed at HEAD (pre-existing behavior).
if (!silent) {
await prompts.log.warn(`Community module '${moduleCode}' has no curator-approved SHA in the registry; installing from main HEAD.`);
}
} else if (planEntry.channel === 'pinned') {
// We cloned the tag directly above (via --branch), but ensure HEAD matches.
// No additional checkout needed.
}
// else: 'next' channel — already at origin/HEAD from the fetch/reset above.
// Record the resolution so the manifest writer can pick up channel/version/sha.
const installedSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
const recordedVersion =
planEntry.channel === 'pinned' ? planEntry.pin : planEntry.channel === 'next' ? 'main' : approvedTag || installedSha.slice(0, 7);
CommunityModuleManager._resolutions.set(moduleCode, {
channel: planEntry.channel,
version: recordedVersion,
sha: installedSha,
registryApprovedTag: approvedTag || null,
registryApprovedSha: approvedSha || null,
repoUrl: moduleInfo.url,
bypassedCurator,
planSource: planEntry.source,
});
// Install dependencies if needed
const packageJsonPath = path.join(moduleCacheDir, 'package.json');

View File

@ -4,6 +4,13 @@ const path = require('node:path');
const { execSync } = require('node:child_process');
const prompts = require('../prompts');
function quoteCustomRef(ref) {
if (typeof ref !== 'string' || !/^[\w.\-+/]+$/.test(ref)) {
throw new Error(`Unsafe ref name: ${JSON.stringify(ref)}`);
}
return `"${ref}"`;
}
/**
* Manages custom modules installed from user-provided sources.
* Supports any Git host (GitHub, GitLab, Bitbucket, self-hosted) and local file paths.
@ -38,8 +45,8 @@ class CustomModuleManager {
};
}
const trimmed = input.trim();
if (!trimmed) {
const trimmedRaw = input.trim();
if (!trimmedRaw) {
return {
type: null,
cloneUrl: null,
@ -52,8 +59,53 @@ class CustomModuleManager {
};
}
// Extract optional @<tag-or-branch> suffix from the end of the input.
// Semver-valid characters: letters, digits, dot, hyphen, underscore, plus, slash.
// Raw commit SHAs are NOT supported here — `git clone --branch` can't take
// them; use --pin at the module level or check out the SHA manually.
// Only strip when the tail looks like a ref, so we don't disturb
// URLs without a version spec or the SSH protocol's `git@host:...` prefix.
let trimmed = trimmedRaw;
let versionSuffix = null;
const lastAt = trimmedRaw.lastIndexOf('@');
// Skip if @ is part of git@github.com:... (first char cannot be stripped as version)
// and skip if @ appears before the path rather than after a ref-shaped tail.
if (lastAt > 0) {
const candidate = trimmedRaw.slice(lastAt + 1);
const before = trimmedRaw.slice(0, lastAt);
// candidate must be ref-shaped and must not itself look like a URL / SSH host
if (/^[\w.\-+/]+$/.test(candidate) && !candidate.includes(':')) {
// Avoid consuming the @ in `git@host:owner/repo` — `before` wouldn't end with a path separator
// in that case. Require that the @ comes after the host/path, not inside the auth segment.
// Rule: the @ is a version suffix only if `before` looks like a complete URL or local path.
const beforeLooksLikeRepo =
before.startsWith('/') ||
before.startsWith('./') ||
before.startsWith('../') ||
before.startsWith('~') ||
/^https?:\/\//i.test(before) ||
/^git@[^:]+:.+/.test(before);
if (beforeLooksLikeRepo) {
versionSuffix = candidate;
trimmed = before;
}
}
}
// Local path detection: starts with /, ./, ../, or ~
if (trimmed.startsWith('/') || trimmed.startsWith('./') || trimmed.startsWith('../') || trimmed.startsWith('~')) {
if (versionSuffix) {
return {
type: 'local',
cloneUrl: null,
subdir: null,
localPath: null,
cacheKey: null,
displayName: null,
isValid: false,
error: 'Local paths do not support @version suffixes',
};
}
return this._parseLocalPath(trimmed);
}
@ -66,6 +118,8 @@ class CustomModuleManager {
cloneUrl: trimmed,
subdir: null,
localPath: null,
version: versionSuffix || null,
rawInput: trimmedRaw,
cacheKey: `${host}/${owner}/${repo}`,
displayName: `${owner}/${repo}`,
isValid: true,
@ -79,29 +133,47 @@ class CustomModuleManager {
const [, host, owner, repo, remainder] = httpsMatch;
const cloneUrl = `https://${host}/${owner}/${repo}`;
let subdir = null;
let urlRef = null; // branch/tag extracted from /tree/<ref>/subdir
if (remainder) {
// Extract subdir from deep path patterns used by various Git hosts
const deepPathPatterns = [
/^\/(?:-\/)?tree\/[^/]+\/(.+)$/, // GitHub /tree/branch/path, GitLab /-/tree/branch/path
/^\/(?:-\/)?blob\/[^/]+\/(.+)$/, // /blob/branch/path (treat same as tree)
/^\/src\/[^/]+\/(.+)$/, // Gitea/Forgejo /src/branch/path
{ regex: /^\/(?:-\/)?tree\/([^/]+)\/(.+)$/, refIdx: 1, pathIdx: 2 }, // GitHub, GitLab
{ regex: /^\/(?:-\/)?blob\/([^/]+)\/(.+)$/, refIdx: 1, pathIdx: 2 },
{ regex: /^\/src\/([^/]+)\/(.+)$/, refIdx: 1, pathIdx: 2 }, // Gitea/Forgejo
];
// Also match `/tree/<ref>` with no subdir
const refOnlyPatterns = [/^\/(?:-\/)?tree\/([^/]+?)\/?$/, /^\/(?:-\/)?blob\/([^/]+?)\/?$/, /^\/src\/([^/]+?)\/?$/];
for (const pattern of deepPathPatterns) {
const match = remainder.match(pattern);
for (const p of deepPathPatterns) {
const match = remainder.match(p.regex);
if (match) {
subdir = match[1].replace(/\/$/, ''); // strip trailing slash
urlRef = match[p.refIdx];
subdir = match[p.pathIdx].replace(/\/$/, '');
break;
}
}
if (!subdir) {
for (const r of refOnlyPatterns) {
const match = remainder.match(r);
if (match) {
urlRef = match[1];
break;
}
}
}
}
// Precedence: explicit @version suffix > URL /tree/<ref> path segment.
const version = versionSuffix || urlRef || null;
return {
type: 'url',
cloneUrl,
subdir,
localPath: null,
version,
rawInput: trimmedRaw,
cacheKey: `${host}/${owner}/${repo}`,
displayName: `${owner}/${repo}`,
isValid: true,
@ -255,6 +327,10 @@ class CustomModuleManager {
const silent = options.silent || false;
const displayName = parsed.displayName;
// Pin override: --pin CODE=TAG resolved at module-selection time overrides
// any @version suffix present in the URL.
const effectiveVersion = options.pinOverride || parsed.version || null;
await fs.ensureDir(path.dirname(repoCacheDir));
const createSpinner = async () => {
@ -264,8 +340,23 @@ class CustomModuleManager {
return await prompts.spinner();
};
// If an existing cache exists but was cloned at a different version, re-clone.
// Tracked via .bmad-source.json's recorded version.
if (await fs.pathExists(repoCacheDir)) {
// Update existing clone
let cachedVersion = null;
try {
const existing = await fs.readJson(path.join(repoCacheDir, '.bmad-source.json'));
cachedVersion = existing?.version || null;
} catch {
// no metadata; treat as mismatched to be safe if a version was requested
}
if ((effectiveVersion || null) !== (cachedVersion || null)) {
await fs.remove(repoCacheDir);
}
}
if (await fs.pathExists(repoCacheDir)) {
// Update existing clone (same version as before)
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Updating ${displayName}...`);
try {
@ -274,10 +365,25 @@ class CustomModuleManager {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync('git reset --hard origin/HEAD', {
cwd: repoCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
if (effectiveVersion) {
// Fetch the ref as either a tag or a branch — `origin <ref>` works
// for both, whereas `origin tag <ref>` fails for branch refs parsed
// out of /tree/<branch>/... URLs.
execSync(`git fetch --depth 1 origin ${quoteCustomRef(effectiveVersion)} --no-tags`, {
cwd: repoCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync(`git checkout --quiet FETCH_HEAD`, {
cwd: repoCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
} else {
execSync('git reset --hard origin/HEAD', {
cwd: repoCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
}
fetchSpinner.stop(`Updated ${displayName}`);
} catch {
fetchSpinner.error(`Update failed, re-downloading ${displayName}`);
@ -287,25 +393,44 @@ class CustomModuleManager {
if (!(await fs.pathExists(repoCacheDir))) {
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Cloning ${displayName}...`);
fetchSpinner.start(`Cloning ${displayName}${effectiveVersion ? ` @ ${effectiveVersion}` : ''}...`);
try {
execSync(`git clone --depth 1 "${parsed.cloneUrl}" "${repoCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
if (effectiveVersion) {
execSync(`git clone --depth 1 --branch ${quoteCustomRef(effectiveVersion)} "${parsed.cloneUrl}" "${repoCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
} else {
execSync(`git clone --depth 1 "${parsed.cloneUrl}" "${repoCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
}
fetchSpinner.stop(`Cloned ${displayName}`);
} catch (error_) {
fetchSpinner.error(`Failed to clone ${displayName}`);
throw new Error(`Failed to clone ${parsed.cloneUrl}: ${error_.message}`);
const refSuffix = effectiveVersion ? `@${effectiveVersion}` : '';
throw new Error(`Failed to clone ${parsed.cloneUrl}${refSuffix}: ${error_.message}`);
}
}
// Record the resolved SHA for the manifest writer.
let resolvedSha = null;
try {
resolvedSha = execSync('git rev-parse HEAD', { cwd: repoCacheDir, stdio: 'pipe' }).toString().trim();
} catch {
// swallow — a non-git repo (local path) wouldn't reach here anyway
}
// Write source metadata for later URL reconstruction
const metadataPath = path.join(repoCacheDir, '.bmad-source.json');
await fs.writeJson(metadataPath, {
cloneUrl: parsed.cloneUrl,
cacheKey: parsed.cacheKey,
displayName: parsed.displayName,
version: effectiveVersion || null,
rawInput: parsed.rawInput || sourceInput,
sha: resolvedSha,
clonedAt: new Date().toISOString(),
});
@ -346,10 +471,26 @@ class CustomModuleManager {
const resolver = new PluginResolver();
const resolved = await resolver.resolve(repoPath, plugin);
// Read clone metadata (written by cloneRepo) so we can pick up the
// resolved git ref + SHA for manifest recording.
let cloneMetadata = null;
if (sourceUrl) {
try {
cloneMetadata = await fs.readJson(path.join(repoPath, '.bmad-source.json'));
} catch {
// no metadata — local-source or legacy cache
}
}
// Stamp source info onto each resolved module for manifest tracking
for (const mod of resolved) {
if (sourceUrl) mod.repoUrl = sourceUrl;
if (localPath) mod.localPath = localPath;
if (cloneMetadata) {
mod.cloneRef = cloneMetadata.version || null;
mod.cloneSha = cloneMetadata.sha || null;
mod.rawInput = cloneMetadata.rawInput || null;
}
CustomModuleManager._resolutionCache.set(mod.code, mod);
}

View File

@ -5,6 +5,46 @@ const { execSync } = require('node:child_process');
const yaml = require('yaml');
const prompts = require('../prompts');
const { RegistryClient } = require('./registry-client');
const { resolveChannel, tagExists, parseGitHubRepo } = require('./channel-resolver');
const { decideChannelForModule } = require('./channel-plan');
const VALID_CHANNELS = new Set(['stable', 'next', 'pinned']);
function normalizeChannelName(raw) {
if (typeof raw !== 'string') return null;
const lower = raw.trim().toLowerCase();
return VALID_CHANNELS.has(lower) ? lower : null;
}
/**
* Conservative quoting for tag names passed to git commands. Tags are
* user-typed (--pin) or come from the GitHub API. Only allow the semver
* character class we use to tag BMad releases; anything else throws.
*/
function quoteShell(ref) {
if (typeof ref !== 'string' || !/^[\w.\-+/]+$/.test(ref)) {
throw new Error(`Unsafe ref name: ${JSON.stringify(ref)}`);
}
return `"${ref}"`;
}
async function readChannelMarker(markerPath) {
try {
if (!(await fs.pathExists(markerPath))) return null;
const content = await fs.readFile(markerPath, 'utf8');
return JSON.parse(content);
} catch {
return null;
}
}
async function writeChannelMarker(markerPath, data) {
try {
await fs.writeFile(markerPath, JSON.stringify({ ...data, writtenAt: new Date().toISOString() }, null, 2));
} catch {
// Best-effort: marker is an optimization, not a correctness requirement.
}
}
const MARKETPLACE_OWNER = 'bmad-code-org';
const MARKETPLACE_REPO = 'bmad-plugins-marketplace';
@ -19,10 +59,25 @@ const FALLBACK_CONFIG_PATH = path.join(__dirname, 'registry-fallback.yaml');
* @class ExternalModuleManager
*/
class ExternalModuleManager {
// moduleCode → { channel, version, ref, sha, repoUrl, resolvedFallback }
// Populated when cloneExternalModule resolves a channel. Shared across all
// instances so the manifest writer (which often instantiates a fresh
// ExternalModuleManager) sees resolutions made during install.
static _resolutions = new Map();
constructor() {
this._client = new RegistryClient();
}
/**
* Get the most recent channel resolution for a module (if any).
* @param {string} moduleCode
* @returns {Object|null}
*/
getResolution(moduleCode) {
return ExternalModuleManager._resolutions.get(moduleCode) || null;
}
/**
* Load the official modules registry from GitHub, falling back to the
* bundled YAML file if the fetch fails.
@ -75,6 +130,7 @@ class ExternalModuleManager {
defaultSelected: mod.default_selected === true || mod.defaultSelected === true,
type: mod.type || 'bmad-org',
npmPackage: mod.npm_package || mod.npmPackage || null,
defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable',
builtIn: mod.built_in === true,
isExternal: mod.built_in !== true,
};
@ -120,10 +176,15 @@ class ExternalModuleManager {
}
/**
* Clone an external module repository to cache
* Clone an external module repository to cache, resolving the requested
* channel (stable / next / pinned) to a concrete git ref.
*
* @param {string} moduleCode - Code of the external module
* @param {Object} options - Clone options
* @param {boolean} options.silent - Suppress spinner output
* @param {boolean} [options.silent] - Suppress spinner output
* @param {Object} [options.channelOptions] - Parsed channel flags. See
* modules/channel-plan.js. When absent, the module installs on its
* registry-declared default channel (typically 'stable').
* @returns {string} Path to the cloned repository
*/
async cloneExternalModule(moduleCode, options = {}) {
@ -161,38 +222,160 @@ class ExternalModuleManager {
return await prompts.spinner();
};
// Track if we need to install dependencies
// ─── Resolve channel plan ─────────────────────────────────────────────
// Post-install callers (config generation, directory setup, help catalog
// rebuild) invoke findModuleSource/cloneExternalModule without
// channelOptions just to locate the module's files. Those calls must not
// redecide the channel — the install step already chose one, cloned the
// right ref, and recorded a resolution. If we re-resolve without flags,
// we'd snap back to stable and overwrite a pinned install.
const hasExplicitChannelInput =
options.channelOptions &&
(options.channelOptions.global ||
(options.channelOptions.nextSet && options.channelOptions.nextSet.size > 0) ||
(options.channelOptions.pins && options.channelOptions.pins.size > 0));
const existingResolution = ExternalModuleManager._resolutions.get(moduleCode);
const haveUsableCache = await fs.pathExists(moduleCacheDir);
if (!hasExplicitChannelInput && existingResolution && haveUsableCache) {
// This is a look-up only; the module is already installed at its chosen
// ref. Skip cloning and return the cached path unchanged.
return moduleCacheDir;
}
const planEntry = decideChannelForModule({
code: moduleCode,
channelOptions: options.channelOptions,
registryDefault: moduleInfo.defaultChannel,
});
// Same-plan short-circuit: a single install calls cloneExternalModule
// several times (config collection, directory setup, help-catalog rebuild)
// with the same channelOptions. The first call resolves + clones; later
// calls with an identical plan and a valid cache should return immediately
// instead of re-running resolveChannel() and `git fetch` (slow; can fail
// on flaky networks even though the tagCache dedupes the GitHub API hit).
if (existingResolution && haveUsableCache && existingResolution.channel === planEntry.channel) {
const samePin = planEntry.channel !== 'pinned' || existingResolution.version === planEntry.pin;
if (samePin) return moduleCacheDir;
}
let resolved;
try {
resolved = await resolveChannel({
channel: planEntry.channel,
pin: planEntry.pin,
repoUrl: moduleInfo.url,
});
} catch (error) {
// Tag-API failure (rate limit, transient network). If we already have
// a usable cache at a recorded ref, treat this as "couldn't check for
// updates" and re-use the cached version silently — that's the right
// call for an update/quick-update, since the semantics don't change
// and the user isn't worse off than before they ran this command.
const cachedMarker = await readChannelMarker(path.join(moduleCacheDir, '.bmad-channel.json'));
if (cachedMarker?.channel && (await fs.pathExists(moduleCacheDir))) {
if (!silent) {
await prompts.log.warn(
`Could not check for updates to ${moduleInfo.name} (${error.message}); using cached ${cachedMarker.version || cachedMarker.channel}.`,
);
}
ExternalModuleManager._resolutions.set(moduleCode, {
channel: cachedMarker.channel,
version: cachedMarker.version || 'main',
ref: cachedMarker.version && cachedMarker.version !== 'main' ? cachedMarker.version : null,
sha: cachedMarker.sha,
repoUrl: moduleInfo.url,
resolvedFallback: false,
planSource: 'cached',
});
return moduleCacheDir;
}
// No cache to fall back on — this is effectively a fresh install with
// no offline safety net. Surface a clear error with actionable guidance.
const isRateLimited = /rate limit/i.test(error.message);
const hint = isRateLimited
? process.env.GITHUB_TOKEN
? 'Your GITHUB_TOKEN may have expired or been rate-limited on its own budget. Try a different token or wait for the reset.'
: 'Set a GITHUB_TOKEN env var (any personal access token with public-repo read) to raise the 60-req/hour anonymous limit.'
: `Check your network connection, or rerun with \`--next=${moduleCode}\` / \`--pin ${moduleCode}=<tag>\` to skip the tag lookup.`;
throw new Error(`Could not resolve stable tag for '${moduleCode}' (${error.message}). ${hint}`);
}
if (resolved.resolvedFallback && !silent) {
if (resolved.reason === 'no-stable-tags') {
await prompts.log.warn(`No stable releases found for ${moduleInfo.name}; installing from main.`);
} else if (resolved.reason === 'not-a-github-url') {
await prompts.log.warn(`Cannot determine stable tags for ${moduleInfo.name} (non-GitHub URL); installing from main.`);
}
}
// Validate pin before we burn time cloning. Best-effort: skip on non-GitHub URLs.
if (planEntry.channel === 'pinned') {
const parsed = parseGitHubRepo(moduleInfo.url);
if (parsed) {
try {
const exists = await tagExists(parsed.owner, parsed.repo, planEntry.pin);
if (!exists) {
throw new Error(`Tag '${planEntry.pin}' not found in ${parsed.owner}/${parsed.repo}.`);
}
} catch (error) {
if (error.message?.includes('not found')) throw error;
// Network hiccup on tag verification — let the clone attempt fail clearly.
}
}
}
// ─── Clone or update cache by resolved channel ────────────────────────
const markerPath = path.join(moduleCacheDir, '.bmad-channel.json');
const currentMarker = await readChannelMarker(markerPath);
const needsChannelReset = currentMarker && currentMarker.channel !== resolved.channel;
let needsDependencyInstall = false;
let wasNewClone = false;
// Check if already cloned
if (needsChannelReset && (await fs.pathExists(moduleCacheDir))) {
// Channel changed (e.g. user switched stable→next). Blow away and re-clone
// to avoid tangling shallow clones of different refs.
await fs.remove(moduleCacheDir);
}
if (await fs.pathExists(moduleCacheDir)) {
// Try to update if it's a git repo
// Cache exists on the right channel. Refresh the ref.
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
try {
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
// Fetch and reset to remote - works better with shallow clones than pull
execSync('git fetch origin --depth 1', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync('git reset --hard origin/HEAD', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
const currentSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
// Force dependency install if we got new code
if (currentRef !== newRef) {
needsDependencyInstall = true;
if (resolved.channel === 'next') {
execSync('git fetch origin --depth 1', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync('git reset --hard origin/HEAD', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
} else {
// stable or pinned — fetch the specific tag and check it out.
execSync(`git fetch --depth 1 origin tag ${quoteShell(resolved.ref)} --no-tags`, {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync(`git checkout --quiet FETCH_HEAD`, {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
}
const newSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
if (currentSha !== newSha) needsDependencyInstall = true;
} catch {
fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.name}`);
// If update fails, remove and re-clone
await fs.remove(moduleCacheDir);
wasNewClone = true;
}
@ -200,22 +383,41 @@ class ExternalModuleManager {
wasNewClone = true;
}
// Clone if not exists or was removed
if (wasNewClone) {
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
try {
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
if (resolved.channel === 'next') {
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
} else {
execSync(`git clone --depth 1 --branch ${quoteShell(resolved.ref)} "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
}
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
} catch (error) {
fetchSpinner.error(`Failed to fetch ${moduleInfo.name}`);
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
throw new Error(`Failed to clone external module '${moduleCode}' at ${resolved.version}: ${error.message}`);
}
}
// Record resolution (channel + tag + SHA) for the manifest writer to pick up.
const sha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
ExternalModuleManager._resolutions.set(moduleCode, {
channel: resolved.channel,
version: resolved.version,
ref: resolved.ref,
sha,
repoUrl: moduleInfo.url,
resolvedFallback: !!resolved.resolvedFallback,
planSource: planEntry.source,
});
await writeChannelMarker(markerPath, { channel: resolved.channel, version: resolved.version, sha });
// Install dependencies if package.json exists
const packageJsonPath = path.join(moduleCacheDir, 'package.json');
const nodeModulesPath = path.join(moduleCacheDir, 'node_modules');

View File

@ -15,6 +15,11 @@ class OfficialModules {
// Tracked during interactive config collection so {directory_name}
// placeholder defaults can be resolved in buildQuestion().
this.currentProjectDir = null;
// Install-time channel flag state. Set by Config.build once, then used as
// the default for every findModuleSource/cloneExternalModule call so that
// pre-install config collection and the install step agree on which ref
// to clone.
this.channelOptions = options.channelOptions || null;
}
/**
@ -38,7 +43,7 @@ class OfficialModules {
* @returns {OfficialModules}
*/
static async build(config, paths) {
const instance = new OfficialModules();
const instance = new OfficialModules({ channelOptions: config.channelOptions });
// Pre-collected by UI or quickUpdate — store and load existing for path-change detection
if (config.moduleConfigs) {
@ -196,6 +201,12 @@ class OfficialModules {
* @returns {string|null} Path to the module source or null if not found
*/
async findModuleSource(moduleCode, options = {}) {
// Inherit channelOptions from the install-scoped instance when the caller
// didn't pass one explicitly. Keeps pre-install config collection and the
// actual install step looking at the same git ref.
if (options.channelOptions === undefined && this.channelOptions) {
options = { ...options, channelOptions: this.channelOptions };
}
const projectRoot = getProjectRoot();
// Check for core module (directly under src/core-skills)
@ -214,13 +225,13 @@ class OfficialModules {
}
}
// Check external official modules
// Check external official modules (pass channelOptions so channel plan applies)
const externalSource = await this.externalModuleManager.findExternalModuleSource(moduleCode, options);
if (externalSource) {
return externalSource;
}
// Check community modules
// Check community modules (pass channelOptions for --next/--pin overrides)
const { CommunityModuleManager } = require('./community-manager');
const communityMgr = new CommunityModuleManager();
const communitySource = await communityMgr.findModuleSource(moduleCode, options);
@ -258,7 +269,10 @@ class OfficialModules {
return this.installFromResolution(resolved, bmadDir, fileTrackingCallback, options);
}
const sourcePath = await this.findModuleSource(moduleName, { silent: options.silent });
const sourcePath = await this.findModuleSource(moduleName, {
silent: options.silent,
channelOptions: options.channelOptions,
});
const targetPath = path.join(bmadDir, moduleName);
if (!sourcePath) {
@ -281,11 +295,24 @@ class OfficialModules {
const manifestObj = new Manifest();
const versionInfo = await manifestObj.getModuleVersionInfo(moduleName, bmadDir, sourcePath);
// Pick up channel resolution recorded by whichever manager did the clone.
const externalResolution = this.externalModuleManager.getResolution(moduleName);
let communityResolution = null;
if (!externalResolution) {
const { CommunityModuleManager } = require('./community-manager');
communityResolution = new CommunityModuleManager().getResolution(moduleName);
}
const resolution = externalResolution || communityResolution;
await manifestObj.addModule(bmadDir, moduleName, {
version: versionInfo.version,
version: resolution?.version || versionInfo.version,
source: versionInfo.source,
npmPackage: versionInfo.npmPackage,
repoUrl: versionInfo.repoUrl,
channel: resolution?.channel,
sha: resolution?.sha,
registryApprovedTag: communityResolution?.registryApprovedTag,
registryApprovedSha: communityResolution?.registryApprovedSha,
});
return { success: true, module: moduleName, path: targetPath, versionInfo };
@ -333,18 +360,37 @@ class OfficialModules {
await this.createModuleDirectories(resolved.code, bmadDir, options);
}
// Update manifest
// Update manifest. For custom modules, derive channel from the git ref:
// cloneRef present → pinned at that ref
// cloneRef absent → next (main HEAD)
// local path → no channel concept
const { Manifest } = require('../core/manifest');
const manifestObj = new Manifest();
await manifestObj.addModule(bmadDir, resolved.code, {
version: resolved.version || null,
const hasGitClone = !!resolved.repoUrl;
const manifestEntry = {
version: resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || null),
source: 'custom',
npmPackage: null,
repoUrl: resolved.repoUrl || null,
});
};
if (hasGitClone) {
manifestEntry.channel = resolved.cloneRef ? 'pinned' : 'next';
if (resolved.cloneSha) manifestEntry.sha = resolved.cloneSha;
if (resolved.rawInput) manifestEntry.rawSource = resolved.rawInput;
}
if (resolved.localPath) manifestEntry.localPath = resolved.localPath;
await manifestObj.addModule(bmadDir, resolved.code, manifestEntry);
return { success: true, module: resolved.code, path: targetPath, versionInfo: { version: resolved.version || '' } };
return {
success: true,
module: resolved.code,
path: targetPath,
// Match the manifestEntry.version expression above so downstream summary
// lines show the cloned ref (tag or 'main') instead of the on-disk
// package.json version for git-backed custom installs.
versionInfo: { version: resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || '') },
};
}
/**

View File

@ -1,6 +1,10 @@
# Fallback module registry — used only when the BMad Marketplace repo
# (bmad-code-org/bmad-plugins-marketplace) is unreachable.
# The remote registry/official.yaml is the source of truth.
#
# default_channel (optional) — the install channel when the user does not
# override with --channel/--pin/--next. Valid values: stable | next.
# Omit to inherit the installer's hardcoded default (stable).
modules:
bmad-builder:
@ -12,6 +16,7 @@ modules:
defaultSelected: false
type: bmad-org
npmPackage: bmad-builder
default_channel: stable
bmad-creative-intelligence-suite:
url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
@ -22,6 +27,7 @@ modules:
defaultSelected: false
type: bmad-org
npmPackage: bmad-creative-intelligence-suite
default_channel: stable
bmad-game-dev-studio:
url: https://github.com/bmad-code-org/bmad-module-game-dev-studio.git
@ -32,6 +38,7 @@ modules:
defaultSelected: false
type: bmad-org
npmPackage: bmad-game-dev-studio
default_channel: stable
bmad-method-test-architecture-enterprise:
url: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise
@ -42,3 +49,4 @@ modules:
defaultSelected: false
type: bmad-org
npmPackage: bmad-method-test-architecture-enterprise
default_channel: stable

View File

@ -0,0 +1,336 @@
const path = require('node:path');
const semver = require('semver');
const yaml = require('yaml');
const fs = require('../fs-native');
const { getExternalModuleCachePath, getModulePath, resolveInstalledModuleYaml } = require('../project-root');
const DEFAULT_PARENT_DEPTH = 8;
/**
* Resolve a module version from authoritative on-disk metadata.
* Preference order:
* 1. package.json nearest the module source/cache root
* 2. module.yaml in the module source directory
* 3. .claude-plugin/marketplace.json
* 4. caller-provided fallback version
*
* @param {string} moduleName - Module code/name
* @param {Object} [options]
* @param {string} [options.moduleSourcePath] - Directory containing module.yaml
* @param {string} [options.fallbackVersion] - Final fallback when no metadata is found
* @param {string[]} [options.marketplacePluginNames] - Preferred marketplace plugin names
* @returns {Promise<{version: string|null, source: string|null, path: string|null}>}
*/
async function resolveModuleVersion(moduleName, options = {}) {
const moduleSourcePath = await normalizeDirectoryPath(options.moduleSourcePath);
const packageJsonPath = await findPackageJsonPath(moduleName, moduleSourcePath);
if (packageJsonPath) {
const packageVersion = await readPackageJsonVersion(packageJsonPath);
if (packageVersion) {
return {
version: packageVersion,
source: 'package.json',
path: packageJsonPath,
};
}
}
const moduleYamlPath = await findModuleYamlPath(moduleName, moduleSourcePath);
if (moduleYamlPath) {
const moduleVersion = await readModuleYamlVersion(moduleYamlPath);
if (moduleVersion) {
return {
version: moduleVersion,
source: 'module.yaml',
path: moduleYamlPath,
};
}
}
const marketplaceVersion = await findMarketplaceVersion(moduleName, moduleSourcePath, options.marketplacePluginNames || []);
if (marketplaceVersion) {
return marketplaceVersion;
}
const fallbackVersion = normalizeVersion(options.fallbackVersion);
if (fallbackVersion) {
return {
version: fallbackVersion,
source: 'fallback',
path: null,
};
}
return {
version: null,
source: null,
path: null,
};
}
async function findPackageJsonPath(moduleName, moduleSourcePath) {
const roots = await buildSearchRoots(moduleName, moduleSourcePath);
for (const root of roots) {
const packageJsonPath = await findNearestUpwardFile(root.searchDir, 'package.json', { boundaryDir: root.boundaryDir });
if (packageJsonPath) {
return packageJsonPath;
}
}
return null;
}
async function findModuleYamlPath(moduleName, moduleSourcePath) {
if (moduleSourcePath) {
const directModuleYamlPath = path.join(moduleSourcePath, 'module.yaml');
if (await fs.pathExists(directModuleYamlPath)) {
return directModuleYamlPath;
}
}
return resolveInstalledModuleYaml(moduleName);
}
async function findMarketplaceVersion(moduleName, moduleSourcePath, marketplacePluginNames) {
const roots = await buildSearchRoots(moduleName, moduleSourcePath);
for (const root of roots) {
const marketplacePath = await findNearestUpwardFile(root.searchDir, path.join('.claude-plugin', 'marketplace.json'), {
boundaryDir: root.boundaryDir,
});
if (!marketplacePath) {
continue;
}
const data = await readJsonFile(marketplacePath);
if (!data) {
continue;
}
const version = extractMarketplaceVersion(data, moduleName, marketplacePluginNames);
if (version) {
return {
version,
source: 'marketplace.json',
path: marketplacePath,
};
}
}
return null;
}
async function buildSearchRoots(moduleName, moduleSourcePath) {
const roots = [];
const seen = new Set();
const addRoot = async (candidate) => {
const normalized = await normalizeExistingDirectory(candidate);
if (!normalized || seen.has(normalized)) {
return;
}
seen.add(normalized);
roots.push({
searchDir: normalized,
boundaryDir: await findSearchBoundary(normalized),
});
};
await addRoot(moduleSourcePath);
if (moduleName === 'core' || moduleName === 'bmm') {
await addRoot(getModulePath(moduleName));
} else {
await addRoot(getExternalModuleCachePath(moduleName));
}
return roots;
}
async function findNearestUpwardFile(startDir, relativeFilePath, options = {}) {
const normalizedStartDir = await normalizeExistingDirectory(startDir);
if (!normalizedStartDir) {
return null;
}
const maxDepth = options.maxDepth ?? DEFAULT_PARENT_DEPTH;
const normalizedBoundaryDir = await normalizeDirectoryPath(options.boundaryDir);
let currentDir = normalizedStartDir;
for (let depth = 0; depth <= maxDepth; depth++) {
const candidate = path.join(currentDir, relativeFilePath);
if (await fs.pathExists(candidate)) {
return candidate;
}
if (normalizedBoundaryDir && currentDir === normalizedBoundaryDir) {
break;
}
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
break;
}
currentDir = parentDir;
}
return null;
}
async function findSearchBoundary(startDir) {
const normalizedStartDir = await normalizeExistingDirectory(startDir);
if (!normalizedStartDir) {
return null;
}
let currentDir = normalizedStartDir;
for (let depth = 0; depth <= DEFAULT_PARENT_DEPTH; depth++) {
if (
(await fs.pathExists(path.join(currentDir, 'package.json'))) ||
(await fs.pathExists(path.join(currentDir, '.claude-plugin', 'marketplace.json'))) ||
(await fs.pathExists(path.join(currentDir, '.git')))
) {
return currentDir;
}
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
break;
}
currentDir = parentDir;
}
return normalizedStartDir;
}
async function normalizeDirectoryPath(candidate) {
if (!candidate) {
return null;
}
const resolvedPath = path.resolve(candidate);
try {
const stats = await fs.stat(resolvedPath);
return stats.isDirectory() ? resolvedPath : path.dirname(resolvedPath);
} catch {
return resolvedPath;
}
}
async function normalizeExistingDirectory(candidate) {
const normalized = await normalizeDirectoryPath(candidate);
if (!normalized) {
return null;
}
if (!(await fs.pathExists(normalized))) {
return null;
}
return normalized;
}
async function readPackageJsonVersion(packageJsonPath) {
const data = await readJsonFile(packageJsonPath);
return normalizeVersion(data?.version);
}
async function readModuleYamlVersion(moduleYamlPath) {
try {
const content = await fs.readFile(moduleYamlPath, 'utf8');
const data = yaml.parse(content);
return normalizeVersion(data?.version || data?.module_version || data?.moduleVersion);
} catch {
return null;
}
}
async function readJsonFile(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
return JSON.parse(content);
} catch {
return null;
}
}
function extractMarketplaceVersion(data, moduleName, marketplacePluginNames = []) {
const plugins = Array.isArray(data?.plugins) ? data.plugins : [];
if (plugins.length === 0) {
return null;
}
const preferredNames = new Set(
[moduleName, ...marketplacePluginNames]
.filter((value) => typeof value === 'string')
.map((value) => value.trim())
.filter(Boolean),
);
const exactMatches = [];
const fallbackVersions = [];
for (const plugin of plugins) {
const version = normalizeVersion(plugin?.version);
if (!version) {
continue;
}
fallbackVersions.push(version);
const pluginNames = [plugin?.name, plugin?.code].filter((value) => typeof value === 'string').map((value) => value.trim());
if (pluginNames.some((name) => preferredNames.has(name))) {
exactMatches.push(version);
}
}
return pickBestVersion(exactMatches.length > 0 ? exactMatches : fallbackVersions);
}
function pickBestVersion(versions) {
const candidates = versions.map(normalizeVersion).filter(Boolean);
if (candidates.length === 0) {
return null;
}
candidates.sort(compareVersionsDescending);
return candidates[0];
}
function compareVersionsDescending(left, right) {
const leftSemver = normalizeSemver(left);
const rightSemver = normalizeSemver(right);
if (leftSemver && rightSemver) {
return semver.rcompare(leftSemver, rightSemver);
}
if (leftSemver) {
return -1;
}
if (rightSemver) {
return 1;
}
return right.localeCompare(left, undefined, { numeric: true, sensitivity: 'base' });
}
function normalizeSemver(version) {
return semver.valid(version) || semver.valid(semver.coerce(version));
}
function normalizeVersion(version) {
if (typeof version !== 'string') {
return null;
}
const trimmed = version.trim();
return trimmed || null;
}
module.exports = {
resolveModuleVersion,
};

View File

@ -1,4 +1,5 @@
const path = require('node:path');
const os = require('node:os');
const fs = require('./fs-native');
/**
@ -69,9 +70,62 @@ function getModulePath(moduleName, ...segments) {
return getSourcePath('modules', moduleName, ...segments);
}
/**
* Path to the local external-module clone cache.
* External official modules (bmb, cis, gds, tea, wds, etc.) are cloned here
* by ExternalModuleManager during install and are not copied into <src>/modules/.
*/
function getExternalModuleCachePath(moduleName, ...segments) {
const base = process.env.BMAD_EXTERNAL_MODULES_CACHE || path.join(os.homedir(), '.bmad', 'cache', 'external-modules');
return path.join(base, moduleName, ...segments);
}
/**
* Locate an installed module's `module.yaml` by filesystem lookup only.
*
* Built-in modules (core, bmm) live under <src>. External official modules are
* cloned into ~/.bmad/cache/external-modules/<name>/ with varying internal
* layouts (some at src/module.yaml, some at skills/module.yaml, some nested).
* This mirrors the candidate-path search in
* ExternalModuleManager.findExternalModuleSource but performs no git/network
* work, which keeps it safe to call during manifest writing.
*
* @param {string} moduleName
* @returns {Promise<string|null>} Absolute path to module.yaml, or null if not found.
*/
async function resolveInstalledModuleYaml(moduleName) {
const builtIn = path.join(getModulePath(moduleName), 'module.yaml');
if (await fs.pathExists(builtIn)) return builtIn;
const cacheRoot = getExternalModuleCachePath(moduleName);
if (!(await fs.pathExists(cacheRoot))) return null;
for (const dir of ['skills', 'src']) {
const direct = path.join(cacheRoot, dir, 'module.yaml');
if (await fs.pathExists(direct)) return direct;
const dirPath = path.join(cacheRoot, dir);
if (await fs.pathExists(dirPath)) {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const nested = path.join(dirPath, entry.name, 'module.yaml');
if (await fs.pathExists(nested)) return nested;
}
}
}
const atRoot = path.join(cacheRoot, 'module.yaml');
if (await fs.pathExists(atRoot)) return atRoot;
return null;
}
module.exports = {
getProjectRoot,
getSourcePath,
getModulePath,
getExternalModuleCachePath,
resolveInstalledModuleYaml,
findProjectRoot,
};

Some files were not shown because too many files have changed in this diff Show More