关于 bmad-loop-resolve
用于处理 bmad-loop 故事暂停的交互式技能,适用于严重升级导致实现无法安全继续的情况。它协助人与会话厘清冻结规格、记录决定并恢复运行,同时处理已保存的审查补丁和工作树隔离。
主要功能
- 01读取升级上下文与冻结规格
- 02提供选项供人类决策
- 03更新规格与解决标记
- 04处理已保存的审查补丁
- 05考虑重新运行时的工作树行为
适用场景
- 01解决规格意图矛盾或缺失
- 02澄清未回答的实现问题
- 03严重升级后恢复运行
- 04在支持时恢复补丁以供审查
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ thecontstruct/ bmad-loop --skill bmad-loop-resolve安装后在 Claude Code 中输入/bmad-loop-resolve确认已加载。
详细介绍
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: ...",
"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 子块(参见下文的故事模式部分);普通升级不会包含此子块。
你的输出标记是 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中的冻结规范——尤其是<frozen-after-approval>区块(开发/审查会话视为权威依据的意图)。升级问题几乎总是因为此区块未提及实现遇到的情况,或与之矛盾。 -
向用户清楚说明当前暂停的证据:
- 当
escalations数组非空时,按现有的最新优先顺序呈现其中记录的条目。不要用paused_reason替代已记录的升级详情。 - 当
escalations数组为空时,首先要求paused_reason是包含至少一个非空白字符的文本。如果缺失、为null、非文本,或去除首尾空白后为空,则报告 resolve 上下文格式错误,不要写入 resolution 标记。否则,原样呈现paused_reason,作为当前暂停的可用证据,并说明没有更新的已记录升级详情。不要读取水位线以下的内容,不要取消筛选或恢复较早工件中的升级信息,也不要根据paused_reason合成升级对象。
使用选定的证据,说明哪些地方含糊或矛盾、为何会阻碍安全实施,并提供 2–4 个具体的解决选项,明确给出建议及其权衡。保持简洁——引用相关规范原文。
- 当
-
取得用户的决定。 如果选择不明确,请追问。不要臆造需求;如果用户拿不定主意,帮助他们分析,不要猜测。
-
更新冻结规范,明确无歧义地记录决定:修改
<frozen-after-approval>区块,以及任何受影响的验收标准/测试矩阵行,使新的开发会话只有一种正确解读。做出消除歧义所需的最小改动。如果需要大幅修改规范,可以使用bmad-spec或bmad-correct-course技能。如果spec_reaches_the_redrive为false,进行相同的修改,然后明确说明此副本不是 re-drive 所读取的副本,并指出redrive_base_ref选定的补救方式:若为分支,必须将更正提交到redrive_base_ref;若为HEAD,必须在主检出中重新应用更正,并保持未提交——未标记的修改会丢失;在错误的工作树或错误分支上提交,则是看似完成、实则会丢失的工作。 -
在
resolution_path写入 resolution 标记(使用上述架构),然后告知用户已记录该决定,他们可以退出本会话——编排器将提供选项,供其重新启用故事并继续运行(基于更正后的规范进行干净重建)。
特殊情况:评审阶段出现 intent gap,且已保存补丁
如果升级是由评审步骤因 intent gap 而中止触发的,开发会话会先将尝试的更改保存为补丁文件(位于实现产物文件夹中),然后还原工作树——升级 detail 和规格说明中的 ## Review Triage Log 都会引用补丁路径。该补丁是具体证据:它准确展示了本次运行实现的是对意图的哪种解读。
首先检查上下文文件中的 restore_supported。 如果其值为 false(工作树隔离运行:重新驱动会丢弃并重新挂载单元的工作树,因此无法原位还原;没有记录规格说明的升级:还原后的补丁没有可恢复的评审;规划前哨兵卡住:没有尝试过的实现可供还原),绝不要提供还原选项,也绝不要记录 restore_patch——编排器会拒绝该解决方案,整个会话的协商都会白费。补丁仍可用作证据。
通过两种方式使用补丁:
-
作为证据。 阅读补丁(以及它所代表的差异),看看推测的解读会产生什么结果——这通常比单看问题更有助于撰写澄清内容。
-
当实际尝试的解读是正确的。 有时本次运行的推测是正确的,只是意图中没有明确说明。将此作为明确选项提供给用户:"实现将其解读为 X,而这确实是我们想要的——修订意图,明确说明 X,然后在已写好的更改上恢复评审,而不是重新实现。" 如果用户选择此项:
- 仍然要在规格说明中修订意图,使其明确无歧义地表述为 X(上文第 4 步不变——冻结的意图必须与还原后的代码一致)。
- 将
"restore_patch": "<the saved patch path>"添加到resolution.json中(从升级详情/分流日志中逐字复制路径)。
随后,编排器会将规格说明重新置为
in-review,把补丁重新应用到基线上,然后再次派发——会话将从还原后的差异进入评审步骤。不要自行执行git apply应用补丁,也不要设置规格说明状态——编排器会在重新启用时以确定方式完成这两项操作。还原操作不得与解决方案提交产生重叠。 重新启用会将重新驱动的基线推进到分支解决后的 HEAD,但已保存的补丁是基于原始基线生成的——因此,如果本会话留下的提交修改了补丁涉及的文件,执行还原时
git apply会失败,故事会再次升级(这是有意为之,且会明确报错:编排器绝不会悄悄将解决方案与过时的尝试合并)。如果解决工作已经包含或取代了尝试的更改,省略restore_patch——重新启用后这些提交会保留为重新驱动的起点,因此从头开始的重新驱动会直接在其基础上构建。
如果尝试的解读是错误的(这是常见情况),则完全省略 restore_patch:编排器会依据修正后的意图从头重新驱动。
你绝不能做的事
- 不要编写协调器的
result.json——那是开发/审查产物;当前不是这类会话。 - 不要更改
sprint-status.yaml,也不要设置规范的status:字段——协调器会在恢复时确定性地重新启用规范状态。只编辑规范内容。 - 不要实现故事、编写功能代码、运行测试或提交。你的工作到修正规范并写入解决标记为止。即使
spec_reaches_the_redrive为false,也仍然如此:将修正后的规范放到重新驱动流程读取的位置是 HUMAN 的步骤——如果redrive_base_ref指向某个分支,就在该分支上提交;如果它是HEAD,就在主检出中重新应用。告诉他们这是必须做的,以及该在哪儿做;不要自行操作。 - 不要扩大范围。只解决被升级的歧义;如果发现无关问题,告知 HUMAN,但不要处理。
Stories 模式:哨兵文件与保留副本
在 stories 模式下,若某个故事甚至无法规划——开发会话在编写真正的规范之前就遇到矛盾或信息缺失——系统会留下一个固定 slug 的哨兵文件,而不是冻结规范:<id>-unresolved.md(意图过于模糊,无法规划)或 <id>-ambiguous.md(有多份故事规范与该 id 匹配)。上下文中的 stories.sentinel 区块会指出该文件,并包含会话记录的 blocking_condition。
哨兵文件不是可供编辑的规范——其中没有计划或 <frozen-after-approval> 区块。因此,对于哨兵文件:
- 不要尝试修订冻结规范(步骤 4 中的“编辑冻结规范”不适用——这里并不存在冻结规范)。应解决上游歧义,以便新的规划流程能够成功:通常需要与 HUMAN 一起澄清
SPEC.md(史诗规范),或此故事在stories.yaml中的条目——规划器读取的title/description/invoke_dev_with。 redrive_base_ref决定上游编辑必须落到哪里,与规范的情况完全相同。spec_reaches_the_redrive无法回答这个问题——它针对的是spec_file,而对于哨兵文件,该文件将被删除。你实际编辑的产物是SPEC.md/stories.yaml,它们面临同样的问题:当redrive_base_ref指向某个分支时,重新驱动流程会挂载一个新的工作树,并从该分支的已提交树重新规划,因此未提交的编辑不可见,重新规划会再次生成相同的哨兵文件——告诉 HUMAN 必须在该分支上提交。当它是HEAD时,重新驱动流程会在主检出的工作树中重新规划,并直接读取编辑内容——不要告诉他们提交。协调器会依据相同规则重新启用流程,并会等到该分支包含这些更改后才恢复。- 重新启用时,协调器不会将哨兵文件切换为
ready-for-dev(没有计划可供分发)。它会将哨兵文件的副本保留为{run}/sentinels/<id>-<kind>.md,作为线索,删除原哨兵文件;下一次分发时会从头重新规划该故事(对于spec_checkpoint故事,会再次执行第 1 阶段)。你自己不要触碰哨兵文件。 - HUMAN 决定如何消除歧义后,照常编写解决标记;将
spec_file设为你编辑的文件(例如SPEC.md),如果修复完全在stories.yaml中完成,则可以省略该字段。
并非哨兵:有多个文件匹配该 id
这不同于上文的单文件 <id>-ambiguous.md 哨兵:当 stories/ 中有多个文件匹配 <id>-*.md(例如 3-login.md 和 3-signup.md)时,id 本身在 磁盘上 存在歧义,编排器会卡住该故事,不选择任何一个文件。你可以通过升级原因(ambiguous story file match: <names>)以及上下文中缺少的内容来识别这种状态:没有 stories.sentinel 块,也没有单一的规格路径。
上述自动清除机制不适用——这里没有需要保留并删除的哨兵,而且仅重新布防只会因相同的重复文件再次卡住。解决办法就是清理:与人工审核者一起确定哪个文件才是该故事的实际规格,并删除或重命名另一个文件(如果两个文件都包含实际工作内容,请先合并;重命名时必须将其移出 <id>-* 模式或改用另一个 id)。匹配到一个文件时,会重新派发该规格;匹配到零个文件时,会从头重新规划。然后像往常一样写入解决标记——重新布防并恢复运行后,后续流程会自动处理。
如果无法解决
如果人工审核者决定暂缓、确实无法获取所需信息,或正确的修复超出规格编辑的范围(例如需要修改 PRD/架构),请明确说明,不要写入解决标记。不写入标记就退出,会使故事继续处于升级状态并暂停运行——这是安全的默认行为。编排器不会重新布防没有记录解决结果的故事。