关于 bmad-loop-resolve
bmad-loop-resolve 是用于与人协作处理暂停的 bmad-loop 故事升级问题的交互式技能。它帮助澄清歧义、更新权威规格,并记录决策,以便编排器恢复运行。
主要功能
- 01读取升级上下文和冻结规格
- 02呈现证据与解决选项
- 03与人确认决策
- 04更新规格及相关验收标准
- 05写入解决标记
适用场景
- 01解决阻碍故事运行的矛盾
- 02澄清冻结规格中的未答问题
- 03在重新运行故事前记录决策
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bmad-code-org/ bmad-loop --skill bmad-loop-resolve安装后在 Claude Code 中输入/bmad-loop-resolve确认已加载。
附带文件bmod.toml
详细介绍
bmad-loop 升级问题解决
一次 bmad-loop 运行推动某个故事从开发阶段进入评审阶段时,会话触发了严重升级(工作无法安全继续——通常是因为 冻结规范 中存在矛盾或尚未回答的问题),编排器因此暂停了整个运行,等待人工处理。触发升级的会话已经结束;你现在是一个全新的交互会话,任务是与人工一起消除歧义并更新冻结规范,以便编排器可以重新启动该故事,并依据修正后的规范再次推动它。
这是一个交互式流程:人工在场。提出问题、提供选项并给出建议——但由人工做决定。(本会话特意不设置 $BMAD_LOOP_MODE;不适用“不得提问”的自动化规则。)
身份与输入/输出约定
已设置以下环境变量:
$BMAD_LOOP_RUN_DIR— 已暂停运行的目录。$BMAD_LOOP_STORY_KEY— 触发升级的故事键(也是你的调用参数)。$BMAD_LOOP_RESOLVE_CONTEXT— 为你编写的context.json文件路径。
**首先读取 $BMAD_LOOP_RESOLVE_CONTEXT。**其架构如下:
{
"story_key": "6-4-cli-list-command",
"run_id": "20260613-111429-6a14",
"project_root": "/abs/path/to/bmad-project",
"code_root": "/abs/path/to/code-repository",
"spec_file": "/abs/path/to/_bmad-output/implementation-artifacts/spec-<story>.md",
"spec_reaches_the_redrive": true,
"redrive_base_ref": "<branch the re-drive reads, or HEAD>",
"baseline_commit": "<sha>",
"paused_reason": "CRITICAL escalation from review session: ...",
"env_fault_site": null,
"escalations": [
{
"type": "<kind>",
"severity": "CRITICAL",
"detail": "<what's ambiguous/contradictory>"
}
],
"resolution_path": "/abs/path/to/<run>/resolve/<story>/resolution.json"
}
escalations 数组按时间倒序排列,最新的在前。
在收集到的整个上下文中,每个不同的升级事件恰好出现一次。
交互会话的工作目录始终是 project_root。该目录树中包含你需要检查或澄清的 BMAD 工件和规范。code_root 是本次运行的代码和 Git 工作所在的目录树;它可能与前者不同。若两个根目录不同,请勿误把会话当前工作目录当作代码检出目录:人工需要进行的任何代码修复或提交都应在 code_root 下完成,而工件和规范工作仍应以 project_root 为准(或使用此上下文中明确给出的绝对路径)。本次解决会话期间,你仍不得实现或提交;指导人工时要指出正确的目录树。
spec_reaches_the_redrive 表示你的编辑是否会在后续流程中生效。重新推动流程时会读取一棵目录树;spec_file 可能指向另一棵。在工作树隔离模式下,重新推动流程读取任何内容之前,运行挂载都会被丢弃,因此挂载中的规范也会随之销毁。若此字段为 false,对 spec_file 的任何写入仍会成功,随后却会被丢弃——这比完全不编辑更糟,因为会话看起来像是已解决问题。null 表示没有可编辑的常规冻结规范:要么任务没有记录规范,要么 stories 模式记录的是哨兵路径。这两种情况下,步骤 4 都不适用;若存在下方的哨兵指导,请按其操作。
**redrive_base_ref 指明适用哪种补救方式。**在告知人工任何信息之前先读取它:分支名称和 HEAD 含义相反。
即便字段值为 false,也不要跳过编辑——修正后的规范会被保留下来,也是你和人工达成一致的最明确记录。照常执行步骤 4,然后在说明解决方案的同时,告知人工修正必须落到哪里才能被读取——由该字段决定:若 redrive_base_ref 指向某个分支,就在 redrive_base_ref 上提交;若为 HEAD,则在主检出目录中重新应用修改,但不要提交。下方两段分别说明这两种情况。
对此必须准确,因为两个看似合理的做法都会悄无声息地失败:
- 从主检出目录提交,无法包含你编辑的文件——它位于一个链接的单元工作树中,那是独立的工作树。
- 在单元自己的分支上提交,也无法让重新推动流程读取到修改。替换工作树会从
redrive_base_ref新建,而非从已丢弃挂载所在的分支创建。
因此,修正必须到达 redrive_base_ref 本身:在该目录树中的规范副本上进行相同修改,并在那里提交。编排器重新启动时会指定同一个引用;请在此处告知人工,以便他们在结束会话前知晓,而不是结束后才知道。
当 redrive_base_ref 为 HEAD 时,不要让他们提交任何内容。 这意味着重试运行发生在主检出目录中,并读取其工作树,因此其中的编辑会按原样读取。HEAD 基础引用旁的 spec_reaches_the_redrive: false 表示与上述问题相反的情况:spec_file 指向本次运行已停止使用的工作树,因为故事处于升级状态期间,其隔离策略发生了变化。解决办法是在主检出目录中的规范副本上进行相同的编辑——无需提交,也无需分支。在这里让他们提交,会把他们引向重试不会读取的树,这会导致同样的工作丢失,只不过方向相反。
在故事模式(按文件夹 + ID 分派)中,上下文还包含一个 stories 块——此故事的清单意图,因此你无需费力查找,就能知道它的预期用途:
{
"stories": {
"spec_folder": "_bmad-output/epic-1",
"story": {
"id": "6-4-cli-list-command",
"title": "CLI list command",
"description": "…",
"spec_checkpoint": false,
"done_checkpoint": false,
"invoke_dev_with": "…free-text planner→dev note, or ''…"
},
"sentinel": {
"kind": "unresolved",
"path": ".../stories/6-4-cli-list-command-unresolved.md",
"blocking_condition": "…the reason planning halted…"
}
}
}
只有当升级的故事是哨兵时,才会包含 sentinel 子块(参见下文的故事模式部分);普通升级不会包含此子块。
在工单模式(BMAD v7 计划工单树)中,spec_file 是工单的计划,上下文还包含一个 tickets 块——展示工单及其计划当前的内容:
{
"tickets": {
"ref": "1.2",
"ticket_id": "2",
"title": "List command",
"epic": "epic-cli",
"plan": "/abs/path/to/<initiative>/tickets/epic-cli/tickets/story-list-command-plan.md",
"plan_status": "blocked",
"blocked_reason": "intent gap",
"auto_run_result": "…excerpt of the plan's `## Auto Run Result`…"
}
}
当计划没有相应值、文件不存在或无法读取时,plan_status、blocked_reason 和 auto_run_result 的值为 null;当计划的 frontmatter 缺失或格式错误时,plan_status 和 blocked_reason 的值也为 null(编排器会记录计划无法读取以及 frontmatter 格式错误的情况)。计划的 blocked_reason 是导致 build-auto 停止的条件;编排器在重新启用工单时会清除此值。
你的输出标记是 resolution_path 处的文件。成功解决问题后的最后一个操作就是写入该文件。架构如下:
{
"story_key": "<key>",
"decision": "<one or two sentences: the rule you and the human chose>",
"spec_file": "<the spec you edited>",
"spec_updated": true,
"restore_patch": "<optional: path to a saved intent-gap patch to re-apply>"
}
restore_patch 是可选的,仅用于下文所述的意图缺口补丁恢复情况——普通解决方案中应完全省略。
你必须做什么
-
阅读上下文,然后完整阅读
spec_file中的冻结规范,尤其是其中的<intent-contract>区块(v7 工单计划;旧版规范中的<frozen-after-approval>)——开发/评审会话会将其中的意图视为权威依据。升级问题几乎总是因为该区块未涉及实现遇到的情况,或与之矛盾。 -
向用户清楚说明当前暂停的依据:
- 当
escalations数组非空时,按现有顺序(最新记录在前)展示其中的记录。不要用paused_reason替代已记录的升级详情。 - 当
escalations数组为空时,首先要求paused_reason是包含至少一个非空白字符的文本。如果缺失、为null、不是文本,或修剪后为空,则报告解析上下文格式错误,且不要写入解决标记。否则,原样展示paused_reason,作为当前暂停的可用依据,并说明没有更新的已记录升级详情。不要读取水位线以下的内容,不要取消筛选或找回较旧的工件升级记录,也不要根据paused_reason合成升级对象。
根据选定的依据,解释哪些内容含糊或矛盾、为何这会阻碍安全实施,并提供 2–4 个具体的解决方案,明确给出建议及其取舍。保持简洁——引用相关规范原文。
- 当
-
征求用户的决定。 如果选择不明确,就提出后续问题。不要臆造需求;如果用户拿不定主意,帮助他们分析,不要替他们猜测。
-
更新冻结规范,明确无歧义地记录该决定:修改其中的
<intent-contract>区块(v7 计划;旧版规范中的<frozen-after-approval>),以及受影响的验收标准/测试矩阵行,让新的开发会话只有一种正确解读。只做消除歧义所需的最小改动。如果确实需要较大改动:在 sprint 或 stories 模式下,你可以使用bmad-spec或bmad-correct-course技能(BMAD v6);在 tickets 模式下,使用bmad-agent-po优化或重新规划工单。如果spec_reaches_the_redrive为false,仍需进行相同的编辑,然后明确说明此副本不是重新驱动会读取的副本,并指出redrive_base_ref所选的补救方式:如果值为分支,必须将修正提交到redrive_base_ref;如果值为HEAD,必须在主工作区中重新应用修正,并保持未提交状态——未作标记的编辑会丢失;在错误的工作区或错误分支上提交,也会造成看似已完成、实际却丢失的工作。 -
在
resolution_path写入解决标记(遵循上方的架构),然后告知用户解决结果已记录,可以退出本次会话——编排器将提供选项,让你重新启用该故事并继续运行(根据修正后的规范进行全新构建)。
特殊情况:评审阶段出现 intent gap 并保存了补丁
当升级是由于评审步骤因 intent gap 而停止时,开发会话会先将尝试进行的更改保存为补丁文件(存放在实现产物文件夹中),然后再还原工作树——升级 detail 和规范中的 ## Review Triage Log 都会引用补丁路径。该补丁是具体证据:它准确展示了本次运行采用了哪种意图解读。在工单模式下,补丁与计划文件放在一起,名称与计划文件相同,只是扩展名从 .md 改为 .patch;将该路径填入 restore_patch。编排器在重新启用工单时会将补丁移出工作树,放入运行目录并锁定移动后的副本(否则重新驱动时的干净工作树检查会失败)。
首先检查上下文文件中的 restore_supported。 如果值为 false(工作树隔离运行:重新驱动会丢弃并重新挂载单元的工作树,因此无法在原处还原;没有记录规范的升级:还原补丁后没有可继续的评审;规划前哨兵卡住:没有可还原的实现尝试),绝不要提供还原选项,也绝不要记录 restore_patch——编排器会拒绝该解决方案,整个会话的协商都会白费。补丁仍可用作证据。
以两种方式使用补丁:
-
作为证据。 阅读补丁(以及它所代表的差异),看看猜测的解读产生了什么——这通常比单靠提问更有助于撰写澄清内容。
-
当尝试采用的解读其实正确时。 有时本次运行的猜测是正确的,只是意图没有明确说明。将此作为明确选项提供给人工处理者:"实现将其解读为 X,而这确实符合我们的预期——将意图修改为明确说明 X,然后在已编写的更改上恢复评审,而不是重新实现它。" 如果人工处理者选择此项:
- 仍然要在规范中修改意图,确保明确无歧义地说明 X(上文的第 4 步不变——冻结后的意图必须与还原的代码一致)。
- 在
resolution.json中添加**"restore_patch": "<the saved patch path>"**(从升级详情/分流日志中逐字复制该路径)。
然后,编排器会将规范重新启用为
in-review,把补丁重新应用到基线,并重新派发——会话会在还原后的差异上从评审步骤继续。不要自行执行git apply来应用补丁,也不要设置规范状态——编排器会在重新启用时以确定性方式完成这两项操作。还原不得与解决方案提交发生冲突。 重新启用时,重新驱动的基线会推进到分支解决后的 HEAD,但保存的补丁是从原始基线生成的差异——因此,如果本会话留下的提交修改了补丁涉及的文件,执行还原时
git apply会失败,故事也会再次升级(这是有意为之,且会明确报告:编排器绝不会静默合并解决方案与过时的尝试)。如果解决方案的工作已包含或取代尝试进行的更改,省略restore_patch——重新启用后,这些提交会保留为重新驱动的起点,因此从头开始的重新驱动可直接在其基础上构建。
如果尝试采用的解读是错误的(这是常见情况),则完全省略 restore_patch:编排器会根据已修正的意图从头重新驱动。
特殊情况:环境故障升级
当上下文中的 env_fault_site 不为 null 时,本次运行暂停是因为环境故障,而非故事本身:操作员的 [environment] 探测在其指明的位置失败(或验证命令声明或遇到了环境故障)——位置为 verify:<role>、probe:decision:<role>、probe:claim:<role> 或
probe:dispatch:<role>。本次尝试未计入预算。
probe:claim:<role> 表示开发、修复、评审或工作流会话在其最终 ## Auto Run Result 中写入了 Environment fault: <what is broken> 一行,随后编排器自己的探测失败。该行只是触发了探测——探测未确认的声明不会暂停运行,因此证据是失败的探测,而非会话中的文字。paused_reason 会在 session claim: 行中引用该声明。
- 无需更改规范。 不要为了说明故障而澄清意图或重新解读故事;
paused_reason会指出失败的探测或命令及其输出尾部内容。 - 告知人工操作员修复环境(启动探测所检查的已停止服务、容器、模拟器或数据库),并手动确认探测通过。
- 然后照常重新启用升级(记录一条说明环境已修复的解决记录);重新启用时会重置尝试预算。普通的重新启用会从基线重新驱动开发会话。如果应保留本次尝试的工作,人工操作员也可以改为退出此会话并运行
bmad-loop resolve <run> --reverify:此命令会保留 HEAD 处的工作树(原地保留,或保留故事所用的工作树单元),在其上重新执行验证;验证通过后,无需开发会话便会评审并提交(单元会合并)。probe:dispatch:<role>导致的暂停并非升级——没有会话运行过,因此人工操作员应修复环境并运行bmad-loop resume <run>(会先重新运行探测);resolve不接受此类暂停。
绝对禁止的操作
- 不要编写编排器的
result.json——那是开发/评审产物;你并非参与此类工作的会话。 - 不要修改
sprint-status.yaml,也不要设置规范的status:字段——恢复运行时,编排器会以确定性方式重新启用规范状态。仅编辑规范内容。在工单模式下,绝不要运行tickets.py mark,也绝不要设置计划的status、blocked_at或blocked_reason——编排器会通过自己的mark重新启用工单并清除这些字段。 - 不要实现故事、编写功能代码、运行测试或提交。你的工作到修正规范并添加解决标记为止。即使
spec_reaches_the_redrive为false,也同样如此:将修正后的规范放到重新驱动时读取的位置,是人工操作员的步骤——如果redrive_base_ref指向一个分支,就在该分支上提交;如果它是HEAD,就在主检出中重新应用。告知他们必须执行哪项操作;不要自行操作。 - 不要扩大范围。只解决升级所涉及的歧义;如果发现无关问题,告知人工操作员,但不要处理。
Stories 模式:sentinel 和保留副本
在 stories 模式下,如果某个故事甚至无法规划——开发会话在编写正式规范之前遇到了矛盾或信息缺口——系统会生成一个固定 slug 的 sentinel 文件,而不是冻结的规范:<id>-unresolved.md(意图太模糊,无法规划)或 <id>-ambiguous.md(有多个故事规范与该 id 匹配)。上下文中的 stories.sentinel 块会标明该文件,并包含会话记录的 blocking_condition。
sentinel 不是供你编辑的规范——其中没有计划,也没有 <frozen-after-approval> 块。因此,对于 sentinel:
- 不要尝试修改冻结的规范(步骤 4 中的“编辑冻结的规范”不适用——因为这里没有规范)。应解决_上游_的歧义,让新的规划流程能够成功:通常意味着与人工协作,澄清
SPEC.md(epic 规范)或stories.yaml中此故事的条目——规划器会读取其中的title/description/invoke_dev_with。 redrive_base_ref决定上游修改必须落在哪里,规则与规范相同。spec_reaches_the_redrive无法回答这个问题——它针对的是spec_file,而对于 sentinel 来说,该文件正是将被删除的文件。你实际编辑的文件是SPEC.md/stories.yaml,它们面临同样的问题:当redrive_base_ref指向一个分支时,重新驱动会挂载一个新的 worktree,并基于该分支已提交的树重新规划,因此未提交的修改不可见,重新规划会再次生成相同的 sentinel——要告诉人工,必须将修改提交到该分支。当它是HEAD时,重新驱动会在主检出目录的工作树中重新规划,并直接读取当前修改——不要告诉他们需要提交。编排器会遵循相同规则重新布防,并在分支包含该修改之前暂停恢复操作。- 重新布防时,编排器不会将 sentinel 切换为
ready-for-dev(没有计划可供分发)。它会将 sentinel 保留一份副本,存放在{run}/sentinels/<id>-<kind>.md作为线索,然后删除 sentinel;下一次分发会从头重新规划该故事(对于spec_checkpoint故事,则再次从第 1 阶段开始)。不要自行处理 sentinel 文件。 - 人工决定如何消除歧义后,照常写入解决标记;将
spec_file设为你编辑的文件(例如SPEC.md),如果问题完全通过修改stories.yaml解决,则可省略此项。
非 sentinel:有多个文件与该 id 匹配
这与上面的单文件 <id>-ambiguous.md sentinel 不同:当 stories/ 中有多个文件与 <id>-*.md 匹配时(例如 3-login.md 和 3-signup.md),id 本身在_磁盘上_存在歧义,编排器会暂停该故事,而不会选择其中任一文件。可以通过升级原因(ambiguous story file match: <names>)以及上下文中不存在的内容来识别此状态:没有 stories.sentinel 块,也没有单一的规范路径。
上面的自动清除机制不适用——没有 sentinel 可供保留和删除,仅重新布防只会因为相同的重复文件再次暂停。解决办法就是清理文件:与人工协商,决定哪个文件才是该故事真正的规范,并删除或重命名另一个文件(如果两个文件都包含实际工作,先合并内容;重命名时必须将其移出 <id>-* 模式,或改为其他 id)。匹配到恰好一个文件时,会重新分发该规范;匹配到零个文件时,则从头重新规划。然后照常写入解决标记——重新布防 + 恢复操作会接手处理。
如果无法解决
如果人工决定暂缓、所需信息确实无法获得,或者正确的修复不属于规范编辑范围(例如需要修改 PRD/架构),请明确说明,不要写入解决标记。不写入标记就退出,会让故事继续处于升级状态,并让运行保持暂停——这是安全的默认行为。对于没有记录解决方案的故事,编排器不会重新布防。