dsh-BMAD-Method
把 BMAD-Method 的模块清单、模块开关、更新收进 dsh 的一个 bundle 插件里。
插件不重写 BMAD 的任何 agent / workflow 逻辑,只做安装器的受控桥接层:dsh 侧负责交互、状态与开关持久化,模块文件仍由官方 bmad-method 安装器落盘。
BMAD 本体随插件携带:运行时(_bmad/ 的配置、脚本、7 个模块)直接放在包的 bmad-home/ 里,装完插件就铺到 $DSH_HOME/bmad-method,46 毫秒、不联网、不用点安装;bmad-method 安装器则是普通依赖(node_modules/bmad-method),只在换版本或补外部模块时才动用。要换 BMAD 版本,在本仓库 npm run update-bmad + 重新生成 bmad-home/ 后发新版本,用户更新插件即完成更新。
五条设计约定
| 要求 | 实现 |
|---|---|
| 封装全部模块 | 清单 = 官方注册表(bmad-modules.yaml)+ 内置 core / bmm;bmad_update() 默认一次装齐未废弃的全部模块 |
工作区自动有 _bmad-output |
任一工具调用都会在工作区建 _bmad-output(安装器 --output-folder 的默认值) |
| 插件内开关模块 | bmad_module_toggle 在 profile 补丁里写 / 去 disabled: true,模块文件一个都不动 |
| 自定义用户名与语言 | userName / language 两个设置项(也可作工具参数传);语言默认简体中文 |
工作区不落 _bmad / .agent |
模块装在全局主目录 $DSH_HOME/bmad-method,所有工作区共用;工作区只多一个 _bmad-output |
安装
从 GitHub 装(把 ph1996-cn 换成你的 GitHub 用户名 / 组织名):
dsh plugin add github:ph1996-cn/dsh-BMAD-Method
仓库里没有任何本机绝对路径:BMAD 主目录从
$DSH_HOME(缺省~/.dsh)推导,profile 补丁靠扫~/.dsh/profiles/*/里dsh.profile.bundles认领本插件的那个,补丁路径可用patchPath配置覆盖。发布前只需把下面几处的ph1996-cn换成自己的账号。
或在 dsh 里让 agent 调 plugin_manager:
plugin_manager({ action: "install_bundle", target: "github:ph1996-cn/dsh-BMAD-Method" })
也可以钉到某个 tag / commit(可复现):
plugin_manager({ action: "install_bundle", target: "github:ph1996-cn/dsh-BMAD-Method#v0.1.0" })
开发调试用本地目录:
git clone https://github.com/ph1996-cn/dsh-BMAD-Method.git
dsh plugin add ./dsh-BMAD-Method
装完会把 bundle 记进当前 profile 的 package.json:
"dsh-BMAD-Method": "github:ph1996-cn/dsh-BMAD-Method", // dependencies
"dsh": { "profile": { "bundles": [ ..., "dsh-BMAD-Method" ] } }
控制面行由包自带的 cordis.patch.yml 提供,不需要手写进 profile 补丁。装完重启 dsh,三个工具才会出现。
包名带大写字母,因此不能发到 npm registry(npm 只收小写名);GitHub 安装不受此限制。要同时发 npm 就把
package.json的name改成小写dsh-bmad-method,index.js的export const name与cordis.patch.yml里的name:跟着改。
设置界面
插件导出 Config(Schemastery),dsh 会把它投影成 JSON Schema 渲染成表单;表单下面那半截(模块开关)是 client.js 注册到 plugins.bundle.config 槽位里自己画的,因为 dsh 的设置表单只管标量字段,管不了「一行一个开关」这种结构。
| 字段 | 默认 | 作用 |
|---|---|---|
userName |
空 | 写进 BMAD 核心配置的用户名(留空用系统用户名) |
language |
简体中文 |
沟通 + 文档输出语言 |
outputFolder |
_bmad-output |
工作区输出目录名 |
只有这三项。BMAD 装在哪、补丁文件在哪,都由插件自己管,不需要你填。
卡片下半部分是模块开关
同一张卡片里,三个输入框下面就是每个模块一行 + 一个开关:显示模块名(BMAD Test Architect)、版本、描述、当前状态。勾上 = 参与 dsh,关掉 = 不参与。
它写的是和工具、和 dsh 插件开关同一份 profile 补丁(disabled: true),不是第二份状态。
为什么不直接点插件页「包含的组件」里的开关?因为 dsh 的行标题取自包名(
resolveText(row.meta.title),而row.meta来自packages.metaOf(row.name)),同一个包的多行标题必然一样,7 行全叫dsh-BMAD-Method,分不出谁是谁。那 7 行开关照样能用(状态是同一份),只是没法认人。
浏览器半端调不了工具,所以插件自己开了一条同源接口:
GET /api/bmad-method/modules → 模块列表 + 当前 enabled(第一次打开会顺手把 BMAD 铺好,不用任何按钮)
POST /api/bmad-method/toggle → { module, enabled },写补丁后立即回读返回新状态
只接受本机同源请求(校验 Origin 与 Host 一致),跨站来源返回 403。
改完保存后重启 dsh 生效(插件配置与补丁都在 profile 启动时读取)。工具参数优先级高于这里的配置(例如 bmad_update 传了 userName 就用参数值)。
设置页看不到这张卡片时,按顺序查:① profile 的 package.json 里 dsh.profile.bundles 有 dsh-BMAD-Method;② 插件是启用状态(plugin_manager 列表里非 disabled);③ 全新加载过插件(改过 index.js 后 set_bundle(false) → set_bundle(true));④ 刷新浏览器页面。
三个工具
| 工具 | 必填参数 | 作用 |
|---|---|---|
bmad_modules_list |
无 | 列出全部模块:已装(读 _bmad/)+ 可装(读官方 bmad-modules.yaml),含 installed / enabled / version / type |
bmad_module_toggle |
module、enabled |
开关某模块:在 profile 补丁里写 / 去 disabled: true。不动文件、不装卸 |
bmad_update |
无 | 调用 BMAD 安装器把模块装齐 / 更新,完成后自动刷新列表 |
可选参数:
projectRoot:覆盖自动探测的安装位置registryPath:覆盖模块注册表bmad_update:userName/language/modules/action/channel/dryRun
// 列出全部模块与状态
{}
// 停用 tea(只让它不参与 dsh,文件一个不删)
{ "module": "tea", "enabled": false }
// 装齐全部模块,用户名与语言一起写进去
{ "userName": "张伟", "language": "简体中文" }
// 先看命令,不执行
{ "userName": "张伟", "dryRun": true }
bmad_update 实际执行的命令(实测 6.12.4 通过):
node <插件目录>/node_modules/bmad-method/tools/installer/bmad-cli.js install \
--directory <全局主目录> --action update --yes \
--modules bmb,cis,tea,bmad-loop,gds,core,bmm --tools claude-code --no-shims \
--user-name 张伟 --output-folder _bmad-output \
--communication-language 简体中文 --document-output-language 简体中文
安装器用的是插件依赖里自带的那份(不联网拉 bmad-method@latest),所以能离线跑。
action 只接受 update / quick-update(安装器不接受 install:Invalid action: install. Valid actions: quick-update, update)。
关于 --tools claude-code:安装器的非交互模式(--yes)强制要求至少一个 IDE 标识,取值表里没有 dsh。它决定安装器把 skill 铺到哪个目录(这里是 .claude/skills)。
两个容易误会的点:
--no-shims管的是「已弃用模块的兼容 shim skill」,跟铺不铺 IDE 文件无关(commands/install.js:37-38原文)。加了它照样会铺.claude/skills。- 安装器把 skill 正文只写进 IDE 目录,
_bmad/里只有 config 和脚本(installer.js:141注释:Skills are now in IDE directories — remove redundant copies from_bmad/)。
本插件的做法:这 90 个 skill 直接随插件包发行(skills/,见下一节),安装器铺出来的 .claude/skills 是重复品,装完就删掉——用户机器上不会多出任何 IDE 目录。
BMAD 的 90 个 skill 在哪
<插件安装目录>/skills/ ← 随包发行,不进用户目录
├─ manifest.json ← 归属表:skill 名 → 所属模块(90 条)
├─ bmad-agent-analyst/SKILL.md ← 57 个 bmad-*:core 8 / bmm 21 / bmb 5 / cis 10 / tea 10 / bmad-loop 3
├─ gds-agent-game-architect/SKILL.md ← 33 个 gds-*:gds 模块(Game Dev Studio 用自己的前缀)
└─ …(brainstorming / prd / spec / review / retrospective / ux / party-mode / testarch / cis-* 等)
这些 skill 就是 BMAD 的全部能力(agent 人格 + 工作流正文)。插件通过 dsh 原生的 SkillProvider(ctx.skills.registerProvider)把它们注册给 dsh,做法和 @wenaixi/dsh-ponytail 一样:
- 用户装完插件就自带 90 个 skill,不需要联网、不需要预装 BMAD、也不往
$DSH_HOME/skills写任何东西; - 插件升级 → skill 跟着升级(更新插件即更新 BMAD);
- 读取时把正文里的
{project-root}/_bmad/动态替换成全局主目录的绝对路径({project-root}/_bmad-output保持不变,仍指向你的工作区),所以 skill 里的脚本调用能命中真实的_bmad/scripts。
验证:
# 包内自带(发布后用户机器上就是这样)
(Get-ChildItem "<插件安装目录>\skills" -Directory | Where-Object { $_.Name -match '^(bmad|gds)-' }).Count # 90
# 用户目录应当是 0 —— skill 不该落在这里
Get-ChildItem "$env:USERPROFILE\.dsh\skills" -Directory -Filter 'bmad*' | Measure-Object
用法举例(描述匹配就会触发):
| 你说 | 触发 |
|---|---|
| 「用 Mary 帮我做需求分析」 | bmad-agent-analyst |
| 「帮我 brainstorm 一下」 | bmad-brainstorming |
| 「写个 PRD」 | bmad-prd |
| 「跑一下 code review」 | bmad-code-review |
| 「跑一次回顾」 | bmad-retrospective |
模块开关管一件事:这个模块的 skill 参不参与 dsh。停用某模块后(立即生效):它的 skill 不再出现在 skill 清单里——归属优先读包内的 skills/manifest.json,本机装了模块再用 _bmad/_config/skill-manifest.csv 覆盖,core 的 8 个永远可用。skill 本身随插件包携带,不会被删。
模块清单:全部模块
官方注册表只登记外部模块,core(BMAD Core)与 bmm(BMad Method 本体)是安装器内置的,代码里补齐后一并列出,type: builtin。
core 是地基:bmad_module_toggle 对它返回 not-toggleable,包内补丁也不给它开行(跟着 bmm 一起加载)。deprecated: true 的模块(automator、wds)仍会列出,但安装时会被跳过。
外部模块需要能访问 GitHub(安装器用 git clone 拉各模块仓库)。网络不通时整次安装会失败,此时插件自动退到内置模块 bmm 重试一次,保证 BMAD 至少能用;网络恢复后再跑一次 bmad_update 即可补齐。
模块开关是怎么生效的
一个 bundle 只挂一个实例(包内 cordis.patch.yml 只有控制面一行),所以插件页「包含的组件」里只有它一个。模块开关在插件详情页的「BMAD 设置」卡片里,每个模块一行:
BMad Method bmm · 6.12.4 启用 [●]
BMad Test Architect tea · v1.27.2 启用 [●]
BMAD Core core · 6.12.4 地基 [──] ← 不参与开关
开关状态只存在 profile 的 cordis.patch.yml 里,形状和 dsh 自己的开关完全一样(顶格覆盖条目):
- id: bmad-module-tea
disabled: true
没有这一条 = 启用。卡片里的开关、bmad_module_toggle 工具写的是同一份状态:
- 卡片点开关 →
POST /api/bmad-method/toggle→ 插件按行写/删这条覆盖条目(启用时整条删掉,回到默认)。 - 调
bmad_module_toggle→ 同一个函数,行为一致。 - 停用后真正发生的事:该模块名下的 skill 立刻不再出现在 dsh 的 skill 清单里(归属来自包内
skills/manifest.json,本机装了再用_bmad/_config/skill-manifest.csv覆盖)。停用bmm,21 个bmad-*skill 一起消失;core的 8 个永远在。这就是"模块没参与加载"的可观测证据,比提示词里的一句说明更硬。 - 模块文件一个都不删:
_bmad/bmm/原样躺在磁盘上,重新打开开关就恢复。 - 不用重启:skill 清单每次构建都是现读补丁算出来的;写完补丁插件还会调一次 provider 的
control.invalidate()让宿主立刻重扫,所以当轮就能看到变化。
写入安全
cordis.patch.yml一律按行定点编辑,不做 YAML 往返序列化 —— 你自己写的注释和条目原样保留(有测试守着)。- 写盘走「临时文件 + rename」,写完立刻回读校验;校验失败把原内容写回并返回
write-failed,不会出现"界面说已停用、文件没变"。 - 工具不缓存内存态,
enabled每次从补丁文件现读。
路径与全局安装
BMAD 装在全局主目录,工作区不落 _bmad/。这些路径都由插件自己推断,不暴露成设置项:
| 位置 | 取值顺序 |
|---|---|
| 全局主目录 | 环境变量 DSH_BMAD_HOME → $DSH_HOME/bmad-method(无 DSH_HOME 时 ~/.dsh/bmad-method) |
| 实际安装位置 | 显式 projectRoot(其下有 _bmad/)→ 全局主目录有 _bmad/ → 从会话工作目录向上找 _bmad/ |
| profile 补丁 | DSH_BMAD_PATCH_PATH → DSH_PROFILE_DIR/cordis.patch.yml → DSH_HOME/profiles/<DSH_PROFILE>/cordis.patch.yml |
| 安装器 | 插件依赖 node_modules/bmad-method → 项目内 / 全局 bmad-method → npx 兜底 |
| 注册表 | 插件依赖里的 bmad-modules.yaml → 项目内 bmad-method → 插件内置快照 |
| 工作区输出目录 | <工作区>/_bmad-output,每次调用自动创建 |
老项目里已经有 _bmad/ 也照用(projectRoot 或自动探测命中即按项目内安装处理,返回里 global: false)。
零配置自举
装完插件什么都不用做,BMAD 已经在包里。 第一次调用任一 BMAD 工具,或者第一次打开设置卡片(卡片加载时自己触发自举),插件就会检查 $DSH_HOME/bmad-method/_bmad/:
- 不存在 → 把包内
bmad-home/_bmad/的运行时直接铺过去:7 个模块的配置、脚本、custom/模板一次到位,实测 46 毫秒,不跑安装器、不联网。用户设置(用户名、语言、输出目录)在铺的时候写进每个模块的 config。 - 已存在但缺了某个模块的
_bmad/<code>/→ 同样用模板补齐。 - 已存在但版本与本插件携带的
bmad-method不一致 → 模板版本对不上就退回跑安装器刷新,免得旧模板铺到新版本预期的目录结构上。 - 全齐且版本一致 → 直接跳过,零开销。
也就是说:装插件 = 装 BMAD。只有需要装/更新到某个新版本、或补装外部模块时才会真正调用安装器。
// 想强制走一次真正的安装器(比如换 BMAD 版本、补外部模块)
{ "dryRun": true, "userName": "张伟" } // 先看命令
{ "userName": "张伟", "language": "简体中文" }
模板路线的实测(v6.12.4):
_bmad/约 345 KB / 31 文件(配置 +scripts/*.py+ 每个模块的module-help.csv),打进包毫无压力。- 主目录只多出
_bmad/,不铺任何 IDE 目录(安装器那条路会铺.claude/skills,插件会清掉——skill 由插件包内的 provider 提供)。 custom/是用户自己的定制:补装时先暂存再放回,不会被模板覆盖。
安装器路线的实测(只有版本不一致或手动 bmad_update 才会走):
- 工作区完全干净,只多出
_bmad-output;没有_bmad/、.agent/。 - 模块文件落在
--directory下,_bmad-output也落在--directory下(工作区那个是插件建的)。 - 非交互首次安装必须给
--tools,插件默认claude-code(或沿用manifest.ides);取值表里没有 dsh,它只决定安装器往哪个目录铺 skill,铺完即清理。 - 外部模块靠
git clone,网络不通时整次安装失败 → 自动退到内置bmm重试,保证能用。 - 不传
userName/language时,安装器保留已存配置,不会覆盖。
怎么验证开关写对了
# 看模块覆盖条目(Windows PowerShell)
Get-Content "$env:DSH_PROFILE_DIR\cordis.patch.yml" | Select-String -Context 0,2 "bmad-module-tea"
判据:
- 停用后文件里有
- id: bmad-module-tea且下一行是disabled: true; - 启用后整条消失(回到 bundle 默认值),文件里不再出现
bmad-module-tea; bmad_modules_list返回里modules[].enabled与文件一致,patchPath就是你改的那个文件;- 不用重启:点完开关,下一次会话的 skill 清单里对应模块的 skill 就已经不见了,
_bmad/里的文件一个不少。
更新
更新 BMAD = 更新插件。 在插件仓库里:
npm run update-bmad # = npm install bmad-method@latest --save-exact
npm test # 顺手验一遍
发新版本后,用户更新插件,下次调用工具时 ensureInstalled 发现版本不一致,自动把全局那份刷到同版本(_bmad/core/config.yaml 里的用户名/语言保留,安装器不覆盖已存配置)。
需要手动强制刷新时,跑 bmad_update(先 dryRun: true 看命令)即可。
插件自身走 dsh bundle 常规路径(plugin_manager 升级或 npm update dsh-BMAD-Method)。package.json 声明 engines.dsh(>=0.2.0-rc.1 <0.3.0)与 engines.node,装到不兼容的 dsh 上会被版本闸门拦下。
更新插件本身:一条命令,不用卸载重装
dsh 的 install_bundle 对已装的包会直接返回 already-installed 拒绝掉,没有"更新"这个动作。改代码后要么卸载重装,要么绕过去直接更新依赖 —— 后者一条命令:
cd "$env:USERPROFILE\.dsh\profiles\desktop"
pnpm update dsh-BMAD-Method # 重新解析 github: 依赖,拿最新 commit
然后重启 dsh。(pnpm add dsh-BMAD-Method@github:... 不行 —— spec 没变,pnpm 按 lockfile 里的旧 commit 装,会静默什么都不干。)
如果只是想边改边试,用开发模式更省事:
# 装本地目录而不是 GitHub 版本
dsh plugin add D:\path\to\dsh-BMAD-Method # 或 plugin_manager install_bundle(target=<目录>)
link 安装下改完代码只需重启 dsh,不用重新装包。
目录结构
cordis.patch.yml # bundle 补丁:只插控制面行
index.js # apply(ctx, config):注册三个工具 / 注入模块上下文 / 注册 skill provider
lib/patch.js # cordis.patch.yml 按行编辑 + 原子写 + 回读校验 + 回滚
lib/bmad.js # _bmad/ 发现、全局主目录、自举、注册表、命令组装、安装器调用
lib/skills.js # SkillProvider:从包内 skills/ 提供 90 个 skill
lib/template.js # 包内 bmad-home/ 模板:铺运行时 + 写用户设置
bmad-home/_bmad/ # BMAD 运行时本体(配置 + 脚本 + 7 个模块,345 KB)
skills/ # 90 个 skill(57 bmad-* + 33 gds-*)+ manifest.json 归属表
lib/registry/ # bmad-modules.yaml 内置快照(依赖没落地时的离线兜底)
fixtures/ # 冒烟测试用的 _bmad/ 项目与补丁文件
test/ # node --test
测试
npm test # node --test,12 个用例
覆盖:工具注册、模块发现(已安装 + 可安装 + 内置 core/bmm + deprecated)、停用写 disabled: true、重复停用幂等、列表反映状态、重新启用移除该键、用户注释与既有条目不被破坏、core 不可停用、未装模块拒绝停用、写盘失败返回 write-failed 且原文件不变、更新命令组装(--user-name / 两个「简体中文」/--output-folder _bmad-output/--no-shims/--modules 含全部未废弃模块)、全局安装时工作区只有 _bmad-output 而无 _bmad/.agent、全新环境自动自举、包内模板 46 毫秒铺好 7 个模块并把用户名写进三处 config、模块停用后它的 skill 不再列出而 core 的仍在、插件页模块开关接口(GET/POST + 跨站 403)、打开卡片即自举(GET 一次就把 7 个模块装好,不需要任何按钮)、Config 三个字段全部 .volatile()。
端到端实测通过:全新 DSH_HOME 下第一次调 bmad_modules_list,46 毫秒铺好 7 个模块(viaTemplate: true),core/config.yaml / bmm/config.yaml / config.user.toml 都写进了用户名与「简体中文」,主目录只有 _bmad/、工作区只有 _bmad-output,provider 列出 90 个 skill;第二次调用版本一致直接跳过。测试里用 DSH_BMAD_MINIMAL=1 只装内置模块,保持离线可跑。
已知边界
- 模块文件由插件依赖里的
bmad-method安装器铺到全局主目录,不需要联网下载 bmad-method 本体;但外部模块(bmb/cis/tea/gds/bmad-loop)仍要git clone,需要能访问 GitHub,装过一次后就在本地(失败会自动降级为内置模块,skill 仍可用)。 - 模块开关是热生效的:只改 profile 补丁 + 调一次 provider
invalidate(),不重启、不重装。它唯一影响的是「这个模块的 skill 参不参与 dsh」——_bmad/里的模块文件、包内的 skill 文件都不动,随时可以开回来。也不影响安装器行为:bmad_update仍会按注册表把全部模块装齐(装齐 ≠ 全启用)。 - 唯一要重启的场景是插件自身代码或包内补丁变了(装新版本、改
cordis.patch.yml)——那属于 profile 启动时读取的部分。 - 注册表跟着
bmad-method依赖走;上游新增模块时npm run update-bmad升级依赖即可,内置快照只是兜底。 - 包内 90 个 skill 是安装时的快照:上游模块出新版 skill 时,
npm run update-bmad只会刷新安装器,skill 正文要重新从安装产物里拷一遍(_bmad-full/.claude/skills→skills/,并重生成skills/manifest.json)。 - 模块开关的边界:开关只控制「这个模块的 skill 参不参与 dsh」——停用后立即生效,该模块名下的 skill 从清单里消失(归属优先包内
skills/manifest.json,本机装了再用_bmad/_config/skill-manifest.csv覆盖;core的 8 个永远可用)。_bmad/里的模块文件、包内的 skill 文件都不动,随时可以开回来。它不影响安装器行为:bmad_update仍会按注册表把全部模块装齐(装齐 ≠ 全启用)。 - 插件页「包含的组件」只有一行:一个 bundle 挂一个实例。早期版本给每个模块开一行想让插件页带开关,但 dsh 的行标题只能取包名,7 行会全叫
dsh-BMAD-Method,反而更乱——现在开关在设置卡片里。
No comments yet. Be the first to write one.