OpenRig Mission PROGRESS.md 模板全解:mission-progress.md 的验收语义、脚手架流程与确定性更新机制

原创2026-10-03 15:41:171,828 阅读
文章标签:人工智能AI Agent多智能体Agent 编排代码智能体CLI

OpenRig Mission PROGRESS.md 模板全解:mission-progress.md 的验收语义、脚手架流程与确定性更新机制

本文以 OpenRig 仓库中 packages/cli/src/lib/scope-templates/mission-progress.md 为骨架,系统讲解 mission 层级进度轨(PROGRESS.md)模板的设计意图、渲染实现、CLI 脚手架与更新命令、daemon 侧索引消费,以及 SDLC 约定中围绕该文件的责任边界。读完你可以掌握:模板每一行在说什么、它如何被 rig scope mission create 物化成文件、如何用 rig scope mission progress 以确定性的方式维护验收清单,以及 UI 侧如何把它投影成可浏览的进度树。

模板定位:Mission 的"持久验收状态",不是工作日志

模板开篇的块引用给出了它的第一性定义:

Durable acceptance state for this mission. In-process steps belong in the working agent's todo tool.

这一定位贯穿了整个 OpenRig 的 scope 体系:

  • PROGRESS.md 只存放"已经发生、可以留存"的验收状态(Scope complete、Implementation in progress、QA / review pass、Merge / ship 这类里程碑),而不是逐条的细碎任务清单;
  • 进行中的步骤属于工作 agent 自己的 todo 工具,不属于本文件。这正是模板刻意保持"薄"的原因——它是一根验收轨(acceptance rail),不是第二份 checklist。

模板同时还把读者引向两处权威资料,二者都是 OpenRig 中"mission 工作怎么做"的规范来源:

注意:模板引用的这两处相对路径在安装后会被解析为 $OPENRIG_HOME 下的对应文件,这是模板随 CLI 打包分发时的刻意设计(后文"模板如何被渲染"会说明加载机制)。

模板正文逐行解读:## Acceptance 与四个验收复选框

模板的正文主体只有一个 ## Acceptance 小节,内嵌四个复选框,对应 mission 生命周期中四个典型的"持久验收状态":

## Acceptance

- [ ] Scope complete (all slices shaped)
- [ ] Implementation in progress
- [ ] QA / review pass
- [ ] Merge / ship

四个条目合起来刻画了 OpenRig 中 mission 从"切片成形"到"合并发布"的完整成熟度阶梯:

复选框 语义 何时应被勾选([x])
Scope complete (all slices shaped) 范围收口:所有 slice 已成形(SPEC 层面的形状确定) 切片规划完成后,进入实现之前
Implementation in progress 实现进行中 首个 slice 进入实现即勾选,直到实现全部完成
QA / review pass QA 与评审通过 独立评审或 QA 回合结束且通过
Merge / ship 合并与发布 代码合并到主分支、发布动作完成

这里需要理解 OpenRig 对"复选框状态"的一个核心区分:PROGRESS.md 里的复选框是"证词(testimony)",不是当前验收权威(acceptance authority)。在 docs/reference/sdlc-conventions.md A2 节中明确写道:历史复选框是证词而非当前验收权威;当 slice 选定了 proof policy 时,当前就绪度应由 rig proof show <slice> 从 proof contract 与归因判定中推导,而不是靠手工维护的清单。换言之,## Acceptance 这四行是给人类操作者和后续 agent 看的"里程碑留痕",真正的"当前是否可交付"以 proof 机制为准。

与之对照,slice 层的姊妹模板 packages/cli/src/lib/scope-templates/slice-progress.md 结构略有不同:它包含 ## Current state(记录当前交付状态与下一步动作)和 ## Outcomes(记录带 proof 链接的实质结果并保留历史),并且明确说"当前验收请运行 rig proof show <slice>,本日志不是第二份 checklist"。可见 mission 层模板刻意只保留最高层的验收轨,把细节状态留给 slice 层,这体现了 OpenRig"分级留痕、避免重复记账"的设计。

模板如何被渲染:占位符替换与候选根解析

mission-progress.md 是 Markdown 模板,唯一的动态部分是 H1 中的 {{missionName}}。渲染函数位于 packages/cli/src/lib/scope/templates.ts:

export function renderMissionProgressTemplate(missionName: string): string {
  const raw = fs.readFileSync(resolveTemplate("mission-progress.md"), "utf8");
  return raw.replace(/\{\{missionName\}\}/g, missionName);
}

值得展开的是其底层 resolveTemplate() 的候选根机制(templates.ts)。模板以 .md 文件形式与源码并存,这样它们可以像普通文档一样被编辑;而为了在"本地开发"与"发布后的包布局"两种场景下都稳定加载,解析器按顺序尝试三组候选根:

  1. 开发态:packages/cli/src/lib/scope/ 的上一级 scope-templates/;
  2. 构建态:dist/lib/scope/ 再向上一级的 lib/scope-templates/;
  3. 兜底:src/lib/scope-templates/。

这解释了为什么模板在打包分发后仍能引用 $OPENRIG_HOME/reference/sdlc-conventions.md——它随包携带,安装后在 $OPENRIG_HOME 下对齐同样的目录约定。若模板定位失败,resolveTemplate 会抛出带 fact / consequence / action 三段式信息的 ScopeCliError,提示重装 @openrig/cli 或从含 packages/cli/src/lib/scope-templates/ 的 checkout 运行。

何时物化:rig scope mission create 的脚手架流程

renderMissionProgressTemplate 的唯一调用方是 mission 创建命令 buildMissionCreateCommand(见 packages/cli/src/commands/scope.ts 附近)。一次 rig scope mission create <name> 会按顺序完成:

  1. 渲染 SPEC.md(renderMissionTemplate,mission 层使用 mission-placeholder.md 或 mission-release.md);
  2. 渲染 NOTES.md(可被 --notes false 关闭,模板路径可由 OPENRIG_NOTES_TEMPLATE_PATH 环境变量覆盖,见 templates.ts);
  3. 渲染 PROGRESS.md(const progressBody = renderMissionProgressTemplate(title);,见 scope.ts);
  4. 若名称匹配 release-X.Y.Z,额外渲染 CAPABILITY-DELTA-v<版本>.md(renderCapabilityDeltaTemplate);
  5. 所有渲染成功后才落盘——fs.mkdirSync(absPath) 创建 mission 目录与 slices/ 子目录,随后依次写入 SPEC.md、mission.yaml、PROGRESS.md、CAPABILITY-DELTA-* 与 NOTES.md(见 scope.ts)。

其中 title 由 titleFromSlug 生成:把文件夹名按 -/_ 切分后逐词首字母大写,例如目录 release-0.4.0 得到 "Release 0.4.0"。模板只替换 {{missionName}} 一个占位符,其余字节原样写入,因此你最终得到的 PROGRESS.md 与仓库中的模板保持字节级一致(这也是 rig scope mission repair 能做幂等回填的前提,见下文)。

创建完成后,mission 目录的"文件契约"是:

  • SPEC.md —— 唯一的 authored node 文件,承载 intent 与 mission 级规范;
  • NOTES.md —— 情境/部落知识累积文件;
  • PROGRESS.md —— 本文主角,验收轨;
  • mission.yaml —— manifest,声明 schema: openrig.mission/v0alpha1 与 composition.mission_markdown 的文件映射(progress 对应 PROGRESS.md)。

如何维护:rig scope mission progress 的确定性更新

模板本身不写"怎么更新",但 OpenRig 提供了与之配套的确定性更新动词:rig scope mission progress(以及 slice 层的 rig scope slice progress)。命令定义在 packages/cli/src/commands/scope.ts:

rig scope mission progress <mission> [--add <text>] [--set <text>]
    [--section <heading>] [--status <status>] [--json]

关键约束与语义:

  • --add 与 --set 互斥,且必须恰好给出一个,否则抛出三段式错误;
  • --add <text>:向指定小节追加一行复选框;
  • --set <text>:把"裁剪后文本完全匹配"的那一行的状态改写为 --status 指定值;
  • --section <heading>:--add 的目标小节标题,默认 Rail(DEFAULT_PROGRESS_SECTION);
  • --status <status>:只接受三个词 active | done | blocked(PROGRESS_STATUSES),其余值会被 parseStatus 拒绝并给出可用值列表。

底层是 packages/cli/src/lib/scope/progress-edit.ts 中两个纯函数(字符串进、字符串出,无文件系统副作用):

  • addProgressRow(content, { section, text, status }):目标小节不存在时自动在文件末尾创建 ## <section> 并追加行;存在时在节块内最后非空行之后插入。幂等:相同(section, text, status)的行重复追加是 no-op。防冲突:同文本不同状态的行会被拒绝,并提示改用 --set。
  • setProgressRow(content, { text, status }):按"裁剪后精确匹配"定位行,0 匹配报错(提示可用 rig scope ... show 列出)、多于 1 匹配报"歧义拒绝"(v0 策略是不猜测);匹配后只改写 [ ]/[x]/[~] 中的指示符字符,其余字节原样保留。

状态词到指示符的映射(statusIndicator)非常关键,它必须与 daemon 侧 indexer 的解析契约严格一致:

--status 词 写入的指示符 indexer 解析结果
active [ ](空格) active
done [x] done
blocked [~] blocked

行匹配正则 ROW_RE = /^(\s*)(?:<a href="https://link.gitcode.com/i/52a7030a76b29987030d332c85f8a368" target="_blank">-*]\s+)?\[([ xX~])\]\s+(.+?)\s*$/ 兼容裸 [ ] 与 - [ ]/* [ ] 两种列表写法;bodyStartIndex 确保所有编辑都跳过 YAML frontmatter、绝不触碰 # H1 与无关行。这套"手术式编辑"约束在 [packages/cli/test/progress-edit.test.ts 中被逐一验证:新增行落在正确节块末尾、frontmatter 与 H1 字节级保持、done/blocked 指示符正确写入、重复追加幂等返回 changed: false。

此外,resolveProgressTarget 定义了更新目标的选择顺序(scope.ts):目录中存在 PROGRESS.md 就编辑它;否则若 README frontmatter 声明了 progress_rail: readme-only,则编辑 README 内的 rail;两者都没有则报错并指引先用 rig scope mission repair <target> 回填或 rig scope mission create 重建——progress 动词只更新已有表面,不负责脚手架。

幂等回填:rig scope mission repair

对于历史遗留、缺少 PROGRESS.md 的 mission/slice 目录,rig scope ... repair 提供幂等回填(backfillScopeProgress,scope.ts):

  • 无 README/SPEC 的目录视为"未声明 scope",跳过;
  • 已有 PROGRESS.md 则跳过(already-present);
  • 声明了 progress_rail: readme-only 的目录跳过(尊重显式退出);
  • 否则用 renderMissionProgressTemplate / renderSliceProgressTemplate 生成文件——backfillTitle 镜像创建时的标题推导(mission 去掉 release- 前缀、slice 去掉 NN- 前缀后 titlecase),保证回填产物与 create 时字节一致。

这条路径让"旧树渐进迁移"成为可能:repair 只做加法,不删除、不重写任何遗留字节。

底层消费:daemon 侧如何索引与投影

PROGRESS.md 不只是给人看的文档,它会被 daemon 侧的 indexer 解析成结构化数据,供 UI 的进度浏览视图消费。核心实现位于 packages/daemon/src/domain/progress/progress-indexer.ts:

  • 扫描根由操作者配置:环境变量 OPENRIG_PROGRESS_SCAN_ROOTS=root1:/abs/path,root2:/abs/path(与文件 allowlist 同形的 name:/abs/path 分隔对),旧名 RIGGED_PROGRESS_SCAN_ROOTS 仍可读作兼容回退;
  • 文件名约定:递归扫描 PROGRESS.md(以及 STEERING.md),默认最大深度 6,跳过 node_modules/.git/dist 等目录;
  • 解析规则与编辑函数一一对应:标题取 frontmatter 之后的第一个 # H1;层级来自 ##/###/#### 标题行;复选框行从 [ ]/[x]/[~] 提取状态与文本,缩进(每 2 空格一级)决定嵌套深度;
  • 输出聚合:每个 PROGRESS.md 文件产出 title、rows(heading + checkbox 两类)与 counts { total, done, blocked, active },跨文件再聚合成 aggregate,最终由 GET /api/progress/tree 端点(packages/daemon/src/routes/progress.ts)提供给前端。

正是因为编辑函数(CLI 侧 progress-edit.ts)与解析函数(daemon 侧 progress-indexer.ts)共享同一套"三态指示符 + H1 标题 + 标题层级"契约,CLI 的确定性写入才能保证 UI 的解析永远不错位。这也是模板中 [ ] 复选框写法必须被严格遵守的原因——写错指示符(例如自定义表情符号)会被 indexer 忽略。

SDLC 约定中的责任边界:WHO / WHEN / HOW

docs/reference/sdlc-conventions.md 对 PROGRESS.md 给出了明确的角色与时机约束(mission-slice-sop skill 的 Part A 也浓缩了同一规则):

  • WHO(谁写):编排者(orchestrator)拥有当前状态;每个 agent 记录自己的产出结果;
  • WHEN(何时写):在实质性的交付状态变化时、以及验收时刻;
  • HOW(怎么写):把当前交付状态保持在新版本的 ## Current state;带 proof 链接的实质结果追加到 ## Outcomes;保留历史条目与既有复选框,但不把它们当作当前验收权威;选中 proof policy 时用 rig proof show <slice> 读取当前就绪度;保持 frontmatter 的 stage/verified 诚实。

这条约定还划清了 PROGRESS.md 与 SPEC.md / PROOF.md / NOTES.md 的分工:SPEC 管"要做什么、怎么算完成",PROGRESS 管"现在到哪了、留下什么结果",PROOF 管"验证了什么、怎么验证的",NOTES 管"情境知识与延续记忆"。四者构成 slice/mission 的完整工作表面,而 mission 层的 PROGRESS.md 只保留最高层的验收轨。

最佳实践与常见误区

基于模板设计与上述实现,使用 mission 层 PROGRESS.md 时有几条值得固化的实践:

  1. 不要把 todo 搬进来。模板块引用明言"in-process steps belong in the working agent's todo tool";细粒度任务应该留在 agent 自己的 todo 工具或 NOTES.md,PROGRESS.md 只留持久验收状态。
  2. 历史复选框是证词,不是权威。勾选过的条目永远保留,作为留痕;"当前能否交付"由 rig proof show 按 proof policy 推导。
  3. 用 CLI 而非手改。rig scope mission progress --add/--set 保证写入与 indexer 契约一致、幂等、不触碰无关字节;手改容易破坏指示符形状。
  4. 状态词汇只有三个。active/done/blocked 是唯一被 indexer 理解的词汇,[~] 表示 blocked 而非其他含义。
  5. repair 是幂等回填。旧树迁移用 rig scope mission repair,它只补缺失文件,绝不改写遗留字节。

相关文件速查

用途 仓库路径
mission 进度轨模板(本文主角) packages/cli/src/lib/scope-templates/mission-progress.md
slice 层姊妹模板 packages/cli/src/lib/scope-templates/slice-progress.md
模板加载与渲染函数 packages/cli/src/lib/scope/templates.ts
mission/slice 创建、progress、repair 命令 packages/cli/src/commands/scope.ts
进度轨纯函数编辑器 packages/cli/src/lib/scope/progress-edit.ts
进度轨单元测试 packages/cli/test/progress-edit.test.ts
daemon 侧进度索引器 packages/daemon/src/domain/progress/progress-indexer.ts
进度树 HTTP 端点 packages/daemon/src/routes/progress.ts
SDLC 约定 SSOT docs/reference/sdlc-conventions.md
mission/slice SOP skill packages/daemon/assets/plugins/openrig-core/skills/mission-slice-sop/SKILL.md

简而言之,mission-progress.md 虽然只有十几行,却是 OpenRig"分级留痕、确定性记账"体系的关键一环:它以最小的表面积定义了 mission 的持久验收轨,通过 renderMissionProgressTemplate 物化、progress 动词确定性维护、progress-indexer 结构化消费,最终与 SDLC 约定和 proof 机制一起,让多 agent 协作下的 mission 状态可读、可迁移、可审计。

登录后查看全文
openrig