关于 revmux
revmux 是用于监督多 Agent 并行代码审查的 skill,会启动并监控 claude --print 和 codex exec 子进程。它处理并发执行、卡顿重试和结果汇总;审查范围与上下文由该 skill 的工作流准备。
主要功能
- 01并行启动多个审查子进程
- 02监控卡顿并重试
- 03汇总并去重审查发现
- 04验证发现并返回结果
- 05归档提示词并注入前轮内容
适用场景
- 01并行审查代码变更
- 02复审修复后的代码
- 03对已提交的问题进行分诊
- 04监控长时间运行的审查任务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ umputun/ revmux --skill revmux安装后在 Claude Code 中输入/revmux确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/invocation.mdloop.mdoutput.mdpr.mdpresent.mdtask-dir.mdtriage.mdscripts/agentdeck-window.shanalyze-corpus.pylaunch-revmux.shpreflight.shtask-state.sh详细介绍
revmux —— 受监督的多 Agent 代码审查
revmux 会并行启动并监督 claude --print 和 codex exec 子进程,监控它们是否停滞,重试卡住的进程,并将审查结果输出到 stdout。
它不负责检测范围、不运行 git、不获取 PR,也不修改源代码。本技能负责这一部分。
| 本技能 | revmux |
|---|---|
| 确定审查范围 | 监督、错开启动、重试、降级 |
| 运行 git,收集上下文 | 编排并归档提示词 |
编写 scope.md、goal.md、context/ | 合并、去重、验证 |
| 选择配置方案、审查视角、标志 | 将审查结果输出到 stdout |
| 阅读 JSON、展示、修复、重新运行 | 注入先前轮次的内容 |
激活条件
- “revmux”、“run revmux”、“review with revmux”
- “multi-agent review”、“supervised review”、“parallel agent review”
- “revmux this branch”、“revmux the last commit”、“revmux the uncommitted changes”
- “revmux pr 123”、“revmux this PR”、PR URL——检出部分,参见
references/pr.md - “triage this”、“triage issue 123”、“is this worth doing”、“should we accept this”、“should I close
this”、问题或讨论 URL——针对已提交事项的评审面板,参见
references/triage.md - “another revmux round”、“re-review after fixes”
- “show me”、“I want to watch”、“run it visible”、“in an overlay”——第 4 步中的覆盖层模式,会在屏幕上显示 TUI。这些表达与审查请求一起出现时表示使用覆盖层;单独出现时不构成触发条件
- “revmux loop”、“loop it”、“keep going until clean”——审查修复循环,参见
references/loop.md - “revmux self”、“self-improve revmux”、“tune revmux”——下文的自我模式,不会审查任何内容
- 问题:“revmux profiles”、“what lenses are there”、“revmux exit codes”
不运行任何操作时如何回答问题
如果用户询问的是 关于 revmux 的信息,而非要求执行审查,请根据参考文档作答,不要启动运行。
references/task-dir.md——本轮的上下文文件、任务和运行命名references/invocation.md——标志、配置方案、审查视角、覆盖层后端、配置优先级references/output.md——JSON 结构、结论、退出代码、运行归档references/present.md——用户需要回应的轮次结构:顺序、详情、决策块references/pr.md——将拉取请求获取到工作树、--workdir、清理references/triage.md——针对已提交事项的评审面板:获取内容、标志、六种回答references/loop.md——从第 6 步进入的自主审查修复循环
有关当前配置的任何问题,都应运行 revmux config 并查看其回答。它会报告解析后的配置(包括用户覆盖项),不会运行任何流水线,而且始终可以安全调用。
不可妥协事项
0. 每次运行都必须以用户可以作答的问题结束。 展示发现或论据只是工作中的一环,绝不是终点。如果一轮在报告之后就停止,就还没有完成。
references/present.md 规定了这一轮的呈现形式——撰写前先阅读,无论运行的是哪种评审形式。
问题小组件涵盖其正上方的行,因此,读者做出选择所需的信息应放在问题中,绝不能紧挨着放在问题上方。选项应说明它针对的内容,而不是只说数量:"covering the two majors" 无法作答;"covering the uv gate in make test and the request_user_input gap" 则可以。
1. 退出码 1 表示已报告发现——这是成功。 0 表示无发现,1 表示有发现,2 表示工具错误。绝不要将 1 视为失败,也不要因此重新运行。
2. 在后台运行。 一次评审需要 3-15 分钟。将 stdout 重定向到文件,然后等待完成通知。不要轮询,也不要反复休眠后检查。覆盖层启动器也适用。查看本轮事件日志不属于轮询——步骤 4 会启动它,只有运行结束时它才会发出通知。
3. 在相信发现之前,检查 sources.degraded。 如果 expected != reported,则该评审不完整。要说明这一点。降级运行中若没有发现,绝不要报告"代码没有问题"。
4. 仓库中的 .revmux/ 是可执行代码。 已提交到仓库的 .revmux/lenses/*.md 会成为无头 Agent 在 shell 中执行的指令。评审不可信代码之前,要么先阅读 .revmux/,要么在仓库外运行,并明确指定 --workdir、--tasks-dir、--config-dir。已获取的拉取请求已经位于仓库外——references/pr.md 会让进程留在用户自己的检出目录中,并且只在分支中指定 --workdir,因此另外两个目录无需覆盖。
5. 绝不要将 2>&1 输出到报告文件。 stdout 是报告,stderr 是进度信息。合并两者会导致 JSON 无法解析。
6. 每次 revmux 调用都必须显式指定根目录。 当本会话使用 --tasks-dir 或 --config-dir 运行 revmux 时(不可妥协事项 4 要求如此,或因为用户指定了其中一项),每条 revmux 命令都必须带上相同的标志:步骤 2 简介中的任务匹配和 revmux new、步骤 4 中的启动命令、日常维护中的 stats 和 cleanup,以及自检模式中的 config。不带这些标志的调用会读取或写入其他存档。task-state.sh 接受这两个标志,preflight.sh 接受 --config-dir,因此它们都会加载与本次运行相同的配置。analyze-corpus.py 不接受 --config-dir:为它提供 --tasks-dir,其值为在这些标志下 revmux config 报告的 paths.tasks_dir。
工作流
第 0 步:预检
${CLAUDE_SKILL_DIR}/scripts/preflight.sh [profile] [--lenses] [--config-dir <dir>]
检查 revmux 以及本次调用所需的所有二进制文件。若有缺失,则指出缺失项并以 1 退出。
传入实际会运行的配置档案。 所需的执行器取决于该配置档案的名单;如果不传参数,预检会检查解析出的默认配置档案。因此,使用与默认配置档案名单不同的配置档案运行时,完全不会检查所需项,审查进行到一半时才会发现 CLI 一直缺少相应二进制文件。如果用户以任何形式指定了配置档案,先将其解析为精确名称——这是第 3 步的规则,与作用域无关,因此可在此应用。未经解析就传入的名称会因未知名称而无法通过预检。如果用户没有指定配置档案,就直接运行预检,并在第 3 步选定配置档案后再运行一次。
如果运行会使用 --lenses,就传入它。 该标志会用配置档案自己的基础运行器上的一个 Agent 替换名单,因此所需二进制文件是该基础运行器和各阶段所需的二进制文件,而不是名单中的二进制文件。按普通运行进行检查时,使用 --lenses 的调用可能通过预检,但随后唯一的发现器却无法启动;反过来,普通运行也可能因为一个从未会用到的二进制文件而被拒绝。
如果缺少 revmux:
brew install umputun/apps/revmux # macOS
go install github.com/umputun/revmux/app@latest # installs as 'app'; rename to 'revmux'
git clone https://github.com/umputun/revmux.git && cd revmux && make install
在关联工作树中,每个会话向用户说明一次这意味着什么。 如果 git rev-parse --path-format=absolute --git-dir --git-common-dir 输出的两个路径不同,那么当前就在关联工作树中。然后分两种情况说明:
- 仅当归档位于工作树内时——用
realpath解析revmux config中的paths.tasks_dir(包括不可省略的 6 个标志);如果路径尚不存在,则解析其最近的现存父目录,并在后面补上缺失部分:revmux 会保留该路径中的符号链接,而 git 会解析它们。如果它等于git rev-parse --show-toplevel的结果,或以该结果加上/开头,则位于工作树内。然后说明,删除工作树时会删除此任务在该路径下的各轮数据,自身模式也会失去这些数据。保留工作树或从主检出目录进行审查,才能保留这些数据。不要提议复制:这是向当前检出目录之外写入数据。 - 始终说明: Git 不会将另一个检出目录中未跟踪的
.revmux/文件复制到新的关联工作树中,因此本地配置和提示词覆盖项可能与主检出目录不同。
第 0.5 步:每个代码仓库提供一次项目配置档案
如果仓库中存在 ./.revmux/profile.md,则 revmux config 的 paths.profile_fallback 为 ./.revmux/profile.md。每一轮如果没有自己的 input/profile.md,就会继承它;但不会有任何操作自动创建该文件——无论是 revmux init,还是单独使用此技能,都不会创建。字段为空意味着此处的每次审查都使用通用校准标准。
字段为空时,用一行说明这一点并提出代为编写。只有用户同意时才可以编写,而且绝不能根据差异内容编写:阅读项目自身的规则——CLAUDE.md、AGENTS.md、CONTRIBUTING.md、docs/、linter 配置——然后写明软件的用途、该项目中实际的故障表现、影响范围、报告标准,以及哪些约定是刻意采用的。根据单次更改猜测配置档案,正是这个文件要取代的做法。
只提议一次。如果用户拒绝,本会话中不要再次询问,也不要改写本轮专用的 input/profile.md——它的优先级高于项目文件,而这正是本轮审查说明明确禁止的替代做法。
绝不可在已获取的拉取请求或任何不属于用户的代码树中这样做: .revmux/ 是纳入版本控制的配置,因此在其中编写文件会把审查标准提交到别人的仓库中。
步骤 1:确定审查范围
| 用户的说法 | 范围 |
|---|---|
| 没有特别说明,在功能分支上 | git diff <base>...HEAD |
| 没有特别说明,在 master 上且有未提交的工作 | git diff 和 git diff --staged |
| 没有特别说明,在 master 上且工作区干净 | git diff HEAD~1 |
| “最近 N 次提交” | git diff HEAD~N |
| “自 以来” | git diff <ref>..HEAD |
| “pr 123”、“这个 PR”、PR URL | references/pr.md — 解析、拉取到工作树中,并在那里审查 |
| “issue 123”、“为这个问题分诊”、问题或讨论 URL | references/triage.md — 处理已提交的问题,而非 diff |
| 裸数字,未说明类型 | 依次检查拉取请求、问题、讨论 — 第一个解析成功的结果决定适用上述哪一行 |
| 路径 | 将该子树作为 diff,并附上阅读清单 |
这里只用两个命令,不要再运行其他 git 命令:
git branch --show-current && git status --short
git diff <range> --shortstat
简要统计数据要写入简报。 在本次会话中,这是一行内容,也是子 Agent 执行步骤 1 的全部工作;否则它要花三到五次 git 调用才能得到相同的三个数字,而无论如何,步骤 4 的说明都要基于这些数字。超出规模统计的内容仍由子 Agent 处理:完整 diff、文件列表,以及值得阅读的内容。
上表中有两种情况不能用单个范围表示,因此单条命令无法涵盖。 master 上的未提交工作需要运行 git diff --shortstat 和 git diff --staged --shortstat,并将两者相加后作为规模 — 如果工作已全部暂存,单独运行 git diff --shortstat 会什么也不报告。路径范围没有范围:交接文件数量,或者不交接任何内容并说明原因。不要交接与 scope.md 中的命令相矛盾的数字,因为简报会将其设为依据 — “直接采用给定的规模,不要再次测量”。
拉取请求的形式不同,不是更难处理的 ref 范围。 revmux 不会拉取任何内容,也不会检出任何内容,因此在审查 PR 之前,必须先将其放到磁盘上,并在之后移除检出内容。阅读 references/pr.md 并按其说明操作 — 它涵盖该情况的步骤 1 至 4,并在步骤 5 返回此处。
已提交的问题根本不是变更,因此没有范围可测量,步骤 2 的简报也没有内容可读:它读取 diff 并返回简要统计数据,而问题分诊两者都没有。references/triage.md 将完全取代步骤 2 — 它会拉取问题条目、讨论串和作者历史到 context/,写入本轮内容,并在步骤 5 返回此处。裸数字要先按上表进行检查;如果数字最终指向拉取请求,则使用 pr.md,无论用户怎么称呼它。
只有在确实存在歧义时才提问 — 功能分支上有未提交工作就是典型情况。使用 AskUserQuestion,在此处以及步骤 4 中选择无头模式还是叠加层时提问。问题始终由本次会话提出,而不是子 Agent:子 Agent 不能提问,而猜测范围的子 Agent 会耗费全部成本审查错误的内容。
步骤 2:准备本轮内容 — 由子 Agent 执行
从确定范围到准备好可运行的本轮内容之间的所有工作,都交由子 Agent 处理:阅读 diff、匹配任务、开启本轮,以及编写上下文文件。这需要调用十几次工具,还要阅读好几屏 diff,而其输出并没有回答用户提出的任何问题 — 还会掩盖它正在准备的审查。
此外,在审查开始前,用户等待的时间就是生成时间。 对八轮存档内容的测量显示,耗时为 65 至 156 秒,八轮的实际耗时与输出 token 数始终呈固定比例 — 因此,造成速度缓慢的是生成的文字和轮次数,而非工具延迟。简报中的预算和禁止事项对此至关重要,并非只是为了整洁。
移交简报前,先展开所有路径。 子 Agent 没有加载此技能,因此在它的 shell 中,${CLAUDE_SKILL_DIR} 没有任何意义——请将解析后的绝对路径替换到简报文本中,而不是直接传递变量,否则它的 task-state.sh 步骤会悄无声息地什么也不做。
先说一句,再启动它:Preparing the round for <what is being reviewed>…——用用户自己的说法描述主题,例如“与 master 对比的分支”“PR 123”或“最近 3 次提交”。然后在子 Agent 返回前不要再说任何话:用户已获知的委派步骤会被视为正在进行的工作,而未作说明的停顿则会显得像卡住了。
启动一个general-purpose Agent。将 Step 1 中解析后的范围以及以下简报交给它:
准备一个 revmux 评审轮次。编写文件;不要更改源代码、运行评审或提交任何内容。
以下每条
revmux命令和task-state.sh,都带上<本次会话的 --tasks-dir/--config-dir 参数,或“无额外参数”>。不要在本次会话允许写入的目录树之外写入任何内容,包括
revmux new。 如果某个防护措施只拒绝命令的写法,并指出了在该目录树内执行同一操作的合规形式(拆分命令、加引号或运行原始命令),就按该形式重试一次。任何其他拒绝,或同一操作被拒绝第二次,都应结束准备工作:将拒绝文本写入notes,然后返回。不要通过其他工具、shell 重定向、API 或符号链接重试。范围为:
<步骤 1 中解析出的那一行及其 ref 范围>,规模为<步骤 1 输出的 shortstat>。按原样采用该规模,不要再次测量。 用户明确提出的排除项:<列出这些项,或写“无”>— 将这些内容写入范围的明确排除项中。这些是已经作出的决定,diff 无法展示它们。
- 阅读更改:该范围的 diff 一次,以及文件列表。这就是全部探索。不要完整阅读源文件,不要搜索目录树,也不要按目录再次运行 diff — 指出值得阅读的文件是评审 Agent 的指示,不是需要先验证的说法;本轮之所以存在,就是因为四个 Agent 即将认真阅读这些文件。
- 如果本次会话已提供任务 ID,原样使用并前往步骤 3。否则,先匹配现有任务,再新建任务 — 同一主题使用第二个 ID,会被当作没有历史记录的首轮。
revmux config | jq '.paths.tasks'会列出任务及其id、description、url、branch、base和rounds。优先精确匹配url或branch;若无匹配,则根据当前主题匹配description,并原样复用匹配项的id。只有完全没有匹配项时才派生 ID:pr-<number>、issue-<number>、将/替换为-的分支名、since-<short-sha>或wip-<branch>。不得包含路径分隔符、..、开头的点,也不得是绝对路径。- 运行
<本次会话解析出的 scripts/task-state.sh 绝对路径> <task-id>,如果有上文所述的参数,则一并传入。没有 ID 时,它会以退出码 1 输出用法提示。它会验证 ID,并报告task.md锚点、每一轮以及每轮input/的状态 —prepared(尚未评审)、claimed(已开始评审但未完成)或ran。这就是任务清单;不要再用ls查看任务或轮次来确认它已报告的信息。- 将轮次命名为
NN-label—01-initial、02-after-fix、03-final— 其中NN比已有的最大值大 1,且同一任务的各轮不得混用命名体系。然后运行revmux new --task <id> --run <NN-label>,严格按此命令执行,之前不要运行--help。它会输出每个待写入文件的绝对路径,并指出哪些文件是它创建的。- 只写入这些路径,不得写入其他路径。绝不拼接路径,绝不创建输出中未列出的目录;覆盖已有的
scope.md前必须先读取。 以下每个文件都必须由项目符号列表、路径和命令块组成 — 绝不写散文段落。 名单中的每个 Agent 都会先阅读这些文件,再自行阅读 diff,因此重复描述 diff 中已有的信息会让阅读成本翻倍,而撰写这些文件已经耗费了本轮的大部分实际时间。
scope— 必填,且少于 1500 个字符。说明更改内容、查看更改的命令、规模、需要完整阅读的文件,以及需要忽略的内容。命令使用最简单的写法:git diff master...HEAD,不要写git -c core.pager=cat diff ...— 前置选项会导致子进程的权限匹配失败。goal— 可选,且少于 1000 个字符。说明更改的目的,并附上“仅当……才正确”的清单。如果本轮是分支合并前的最后一轮,就将其设为最终把关轮:说明本轮旨在判断 diff 中是否有任何内容不应发布,并明确哪些情况符合此标准 — 可执行代码中的缺陷、会导致后续运行出错的提示词、架构或已发布脚本的更改,或会误导执行文档的 Agent 的矛盾之处。说明没有发现问题也是有效结论,否则评审者会以为必须找出问题而凭空制造问题。profile— 不要写入。 它记录的是仓库而非本轮的信息,因此应放在./.revmux/profile.md中;对于没有非空覆盖文件的每一轮,revmux 都会提供同一份文件,且不会复制上一轮的内容。你看到的 diff 和文件列表不足以判断仓库规范,而在这里生成的文件会优先于项目中的文件,并悄然取而代之。 只有当用户表示该主题需要采用不同于仓库默认标准的评审尺度时,才写入该文件。context— 可选。包含工单文本、设计说明、提交列表。会报告其路径,但不会创建该目录。task_file— 如果created列出了它,则必须写入;如果它已经存在,但仍是所有锚点都为空的未填写模板,也必须写入。无论哪种情况,都要先读取。填写description,以及已知时的url、branch和base:这些前置元数据是下个会话用于匹配的依据;若task.md留作空白模板,下个会话就会新建重复 ID。- 在最后一次写入后立即停止。写入失败会返回错误,因此之后无需确认 — 不要运行
ls、diff、第二次task-state.sh或git status。不要读取上一轮的
scope.md、goal.md或context/。 revmux 会自动将每个先前轮次的内容注入每个组合提示词中,因此读取它们毫无益处,反而会让本轮范围受上一轮措辞影响。也不要读取任何轮次的profile.md:revmux 解析出的项目文件并不在这些文件之中。仅返回 JSON:
{"task": "", "run": "", "round_dir": "", "scope_path": "", "wrote": [], "files_changed": 0, "insertions": 0, "deletions": 0, "areas": [], "notes": ""}。areas列出 diff 涉及的代码库部分。任何出错情况都写入notes,不要试图掩盖。 仅当子 Agent 报告有无法解决的问题时,才自行查看references/task-dir.md。
其他内容一律不得交给子 Agent。 配置文件的选择、启动、报告以及报告的呈现方式,都必须留在当前会话中——这些正是用户正在等待的决策,而且总结已经作出的决策并不等于作出决策本身。
启动前检查返回内容。 缺少 round_dir 或 wrote 为空,都表示尚未准备好本轮,启动后会因缺少范围而失败;请在此处修复,不要直接启动。
即使 wrote 不为空,notes 中尚未解决的拒绝也会阻止启动:本轮只包含部分上下文。通过命令形式重试后成功的情况也会在此处报告,但不会构成阻碍。告知用户遭到拒绝的内容及其位置,并在问题解决前不要启动。Step 4 的单行公告将根据这些规模数字编写。
第 3 步:选择配置和标志
| 配置 | 人员配置 | 适用场景 |
|---|---|---|
comprehensive(默认) | bugs+impl、arch+quality、docs+tests,以及 codex 同行审查 | 实际变更、实际风险 |
focused | 一个 bugs Agent,加 codex 同行审查 | 小型任务或时间有限 |
final | bugs+impl 加 codex 同行审查,不处理低于 major 的问题 | 合并前 |
claude-only | 同样拆分为四组视角,全都由 claude 处理 | 没有 codex 可用 |
codex-only | 同样拆分为四组视角,由 codex 处理,并由其进行综合和验证 | 没有 claude 可用 |
grill-me | bugs+impl 和 architecture+quality,各由 claude 和 codex 分别审查一次,且都对照变更进行审查 | 用户希望彻底挑错 |
expert | 两个 Agent,均使用 xhigh:codex gpt-6-astra:xhigh 和 claude fable:xhigh,各自涵盖全部八个视角 | 计划,或不容出错的变更。速度慢且成本高;只有用户明确提出时才选,不能设为默认 |
triage | codex 处理 facts(事实依据 + 先例)、thesis、antithesis、cost | 已提交事项,而非差异;需要 --no-synthesis --verify-group-by source、references/triage.md |
绝不要自行选择 expert。 它会让两个 Agent 各自以 xhigh 应用每个视角,成本是 comprehensive 的好几倍,而且审查对象的任何属性都不足以成为选择它的理由——不是计划,不是大型差异,也不是高风险变更。只有用户明确提出时才选择,并说明原因。其他所有配置都会审查指定的对象,包括计划;expert 的严重性门槛只是专门设置为让提案读起来与差异一样自然。
用户说出的配置词不等于配置名称。 将用户的表述映射到 revmux config 报告的配置,优先匹配名称,其次匹配 description。revmux 在加载时会拒绝未知的 --profile,且不会猜测,因此解析工作由此技能负责——原样传递用户未解析的表述只会导致运行失败。
| 用户说 | 配置 |
|---|---|
| 全面、所有内容、深入、详尽、全部都要 | comprehensive |
| 简短、快速、轻量、小型、时间有限 | focused |
| 最终、合并前、合并之前、严格 | final |
| 仅 claude、不要 codex、跳过 codex | claude-only |
| 仅 codex、不要 claude、只用 codex | codex-only |
| 彻底挑错、把问题都找出来、毫不留情、对抗式 | grill-me |
| expert、最佳模型、最高投入、不惜代价、使用 astra 和 fable | expert——且仅限这类明确表述,绝不能根据审查对象推断 |
| 对此进行分诊、值不值得做、是否应该接受、是否应该关闭 | triage,且对象是已提交事项,而非差异——references/triage.md 负责所需标志 |
示例并非完整列表。应根据意图匹配——覆盖面需求对应 comprehensive,速度需求对应 focused,合并门槛对应 final——并查看解析后的目录,而不是只看此表,因为用户可能有表中没有的自定义配置。指出所选配置及其对应的用户表述。只有两个选项确实难以区分时才提问,例如“快速审一下,合并前就要”就介于 focused 和 final 之间。
--lenses a,b 会生成一个同时承担两个视角的 Agent,并移除 codex 同行审查,从而失去所有跨来源的置信度提升。除非明确需要缩小范围,否则优先使用配置。
另外也有用:--min-confidence=70 仅保留可执行项;人工审阅全部内容时使用 --no-verify。
第 4 步:运行——无头模式或叠加模式
两者都会在 stdout 上返回相同的报告,并返回相同的退出代码。
无头模式——默认:
revmux --task <id> --run <name> --no-tui > /tmp/revmux-<id>-<run>.json 2> /tmp/revmux-<id>-<run>.log
在后台启动,等待通知。
审查已获取的拉取请求时,添加 --workdir <worktree>,其他内容不变——包括仍从会话自身的检出目录运行命令。这能确保归档不会落入清理操作会删除的目录。原因见 references/pr.md。
让出控制权前,告诉用户三件事——否则他们会在没有任何反馈的情况下等待 10 分钟以上:
- 正在运行的内容(任务、配置、队伍人数)以及大致耗时
- stderr 日志路径,以及运行
tail -f <path>可查看每个 Agent 的实时进度 - 用户可随时询问状态
收到状态请求时,读取 stderr 日志末尾和轮次目录中的 events.jsonl。报告阶段、当前活跃的 Agent 以及已耗时。绝不猜测。
然后在让出控制权之前启用进度推送——仅限无头模式。 在该轮次自己的 events.jsonl 上运行 Monitor:
tail -n +1 -F <round_dir>/events.jsonl \
| grep -E --line-buffered '"kind":"(stage|agent_started|agent_done|agent_retried|agent_degraded|dropped|rate_limit)"'
将 timeout_ms 设为 1800000,将 persistent 设为 false;当收到该次运行自己的完成通知时,使用 TaskStop 停止它——tail -F 不会自行结束。references/invocation.md 说明了为何选用这七种事件,而不是日志。
每分钟最多播报一次,并且只播报已发生的事情。 将上次播报以来的所有事件合并成一条简短消息——说明哪个阶段已开始、哪些 Agent 已启动或完成——不要每个事件各说一条:一个队伍会在几秒内启动四个 Agent,连续四条这样的消息只会造成另一种干扰。重试、降级或速率限制发生时立即播报,不要等到下一分钟;这些情况会影响用户接下来要做什么。
绝不播报静默时段。 不发心跳消息,不说“自上次检查以来没有变化”,也不重复播报已传达的里程碑——在什么都没发生时仍不断发声的推送,用户会选择关闭。监控器会因文件变更而唤醒,因此保持静默不会造成任何代价,也不需要特意说明。
此推送仅适用于这种模式。 它存在的原因是无头运行会持续十分钟毫无消息,而用户没有其他信号。覆盖层运行本身就是信号:进度实时显示在屏幕上。
覆盖层——用户想要观看时:
${CLAUDE_SKILL_DIR}/scripts/launch-revmux.sh --task <id> --run <name> [any revmux flag]
检测终端(agterm、tmux、zellij、herdr、kitty、wezterm/kaku、cmux、ghostty、iTerm2、Emacs vterm),在覆盖层中运行带 TUI 的 revmux,并在 stdout 返回报告。在 agterm 下:浮动面板占窗格的 80%;若会话已分屏,则使用窗格覆盖层,以保留相邻窗格的实时显示。两种形式都会染成蓝色。不要传入 --no-tui;脚本会拒绝该参数。
- 整个评审期间它都会阻塞,因此应将其放到后台运行,并等待完成通知
- 不启用进度推送,也不播报那三项内容。 TUI 就是进度推送:此时运行
Monitor,会每分钟用文字描述用户正看着实时呈现的内容;而提到tail -f的三句话是在指向一个用户不需要查看的日志。用一句话说明正在运行什么,然后让出控制权。 - 其退出码:
0/1/2属于 revmux,3表示启动器失败,127表示未安装 revmux。3表示评审尚未运行——只有这种情况需要重试。 - 覆盖项:
REVMUX_AGTERM_PERCENT(80;在分屏时强制使用浮动面板)、REVMUX_POPUP_WIDTH/HEIGHT(90%)、REVMUX_AUTO_EXIT(30 秒;0表示等待读者退出)、REVMUX_TMUX_WINDOW=1用于创建可在断开连接后继续运行的 tmux 窗口
除非用户要求观看,或明显正在终端前,否则选择无头模式。
如果启动器在运行完成后退出,报告不会丢失——它位于轮次目录中的 findings.json 和 report.md。从那里读取,不要重新运行。被终止的运行不会生成报告,而且大多数退出码为 2 的运行也不会生成报告——但报告只有在轮次归档后才会输出到 stdout,因此,若某次退出码为 2 的运行在 stderr 中注明写入报告失败,完整轮次仍可能存在。重新运行前,检查该轮次的 manifest.json 是否包含内容。
必须传入 --run。已运行过的轮次会报错,不会被覆盖——但评审尚未完成的轮次不属于这种情况,因此退出码为 2 后重试时,使用相同名称和相同的 input/ 即可。但只有在失败的评审未向轮次中写入任何内容时才适用;实际情况并没有听起来那么宽松:流水线启动时第一件事就是打开 events.jsonl,因此,启动后中断的运行无法再以原名称重试,只能新建轮次并复制 input/。轮次名称不能是 task.md。
步骤 5:读取结果
阅读 references/output.md,了解完整结构。
- 退出码。
2表示没有可用结果——查看 stderr 日志,其中会说明原因。如果原因是写入 stdout 报告失败,则本轮已完成,磁盘上已有其findings.json;请读取该文件。否则修复原因并重新运行:如果该轮仅包含input/,则使用相同的--run;如果 revmux 拒绝该名称,则新建一轮,并复制input/。0和1均表示已完成。 sources。degraded非空表示结果不完整;请优先说明这一点。findings。 按严重程度分组。open_questions、pre_existing和immaterial——分别报告。
sources 保存Agent 名称,是独立佐证的唯一依据。lenses 保存视角名称,仅供参考。一个 Agent 在两个视角下标记问题,仍然只算一个来源。
第 6 步:呈现结果,然后提出建议
现在读取 references/present.md,并将本轮内容写入其中的代码审查框架。 它规定了本轮的顺序、详略的唯一准则、决策区块以及问题本身的规则。本轮的形式只由该文件定义;下面的内容仅说明代码审查需要额外添加什么。
在每条发现的标题前,用一对方括号标注其表面类型和严重级别: [code, minor]。第一个值是表面类型:可执行逻辑用 code,源文件中的注释或文档注释用 comments,项目文档用 docs,测试代码用 tests,其他情况则使用适用的小写词(config、build)。revmux 不会返回表面类型字段,因此要根据 file 和正文所述内容推断:如果 .go 路径对应的正文讨论的是过时的 godoc,应标为 comments,而不是 code。第二个值是发现的确切 severity 字段。不得省略任何一个值,也不得将它们拆成不同标签。标题格式为 [surface, severity] Title — file:line, conf N。
统计数量时也要使用相同的组合标签。“3 条发现”没有说明风险来自哪里;“2 条 [code, major],1 条 [docs, minor]”则说明了。
只有 findings 是可采取行动的内容——即 revmux 保留的内容。其他三类也要报告,但不要计入发现数量:immaterial 表示问题确实存在,但判断认为不值得修复;pre_existing 表示问题确实存在,但不是本次更改造成的;open_questions 表示需要作出决定,而不是进行编辑。
根据结果提出合适的建议:
| 本轮结果 | 建议 |
|---|---|
sources.degraded 非空 | 先重新运行缺失的来源——部分审查没有发现问题,并不能作为没有问题的证据,因此在此之前不值得决定其他事项 |
存在任何 critical 或 major | 先修复,再针对同一任务开始新一轮——如果用户希望完整跑完,也可以选择下面的循环流程 |
只有 minor,且判定结果不为 unverified | 现在修复,或留待之后处理——验证时已权衡每项修复的成本与后果,因此剩下的只是决定何时处理 |
只有 minor,且判定结果为 unverified | 说明尚未检查任何内容,并在采取行动前逐项权衡——--no-verify 不会丢弃任何内容,也不会生成 immaterial 列表 |
open_questions 非空,且没有发现 | 回答这些问题——它们需要作出决定,而审查没有发现需要编辑的内容 |
| 没有任何可采取行动的事项 | 完成;如果接下来要合并,则使用 --profile final |
按顺序检查各行,匹配到的第一行优先,因此即使降级运行报告了其他结果,降级状态仍优先。
将建议放入 AskUserQuestion,先列出建议选项并标注 (Recommended)。
即使发现看起来很明确,也绝不要直接开始修复。 审查会产出发现;编辑是另一个决定,应由用户在此处作出。
在问题中将循环流程作为第三种选择,与停止以及修复后再审查一轮并列:审查、修复、提交、再次审查,直到某一轮不再有阻止事项。该流程只适用于用户自己的分支,绝不能用于已获取的 PR,并且提交时不会推送。references/loop.md 包含完整流程——用户选择该项后再读取,不要提前读取。
用户回答时,只要没有选择再审查一轮,就执行下面的归档检查。
第 7 步:按需修复并重新运行
- 确认要处理哪些发现。
immaterial判定表示 revmux 已经排除了该项——它在独立列表中,不在findings里。rejected项目则不会出现在任何地方:验证器判定它并非真实问题,因此将其删除,只有归档中的阶段快照仍保留它。 - 进行修复。
提交修复前,按以下顺序执行这三项检查。 1. 搜索问题模式,而不是站点。 将发现所揭示的缺陷命名为一种构造—— “只检查了三值枚举一端的 switch”、“无论数量如何都断言某种形状的句子”、“必须与旁边列表匹配的短语”——然后在代码库中搜索这种构造,并在同一提交中修复所有出现之处。搜索模式,而非字面字符串:跨行换行的短语无法通过搜索该短语找到。
之所以有这项检查,是因为另外两项无法发现它能发现的问题。在某个存档任务中,同一缺陷出现了三次;每次修复都只处理了发现中引用的位置,却把其他地方的相同缺陷留了下来——一次在同一提交已在编辑的文件中,一次在旁边的文件中,还有一次在同一个 diff 区块中向下四行处,而该提交本身的消息还写明了通用规则。知道规则不能替代实际搜索。
2. 列出你改动的所有内容。 列出所改对象的输入空间,并确认每个成员都得到处理,而且有测试能区分它们:
- 一个枚举或一组字符串常量——每个值,以及在方向重要时的每种状态转换
- 被折叠、复制或序列化的结构体——每个字段,而不只是此次改动针对的字段
- 代码会按其分支处理的平台、文件系统或执行器——每个分支
- 代码会加以区分的错误类别——不存在、不可读取、格式错误、存在但为空
- 一个比率——分子和分母是否取自同一群体
反复出现的问题并非处处粗心大意,而是针对触发问题的情况编写修复,又用测试锁定同一种情况,导致测试无法发现遗漏之处。
3. 重读发现内容,再对照它检查你的修复。 修复发现指出的机制,而不是它用来举例说明的情况。若发现指出“这个 switch 无法表示跳过 minor 的变更”,那仅仅在 switch 中再添加一个分支并不能解决问题。
然后运行测试和代码检查工具。会导致构建失败的修复不能提交。
3. 在同一任务上开启下一轮,并为其编写专属的 scope——修复内容及其提交范围。再次使用步骤 2 的子 Agent,沿用相同的单行说明、它已返回的任务 ID、前几轮沿用的所有排除项,并为这些修复提供新的 --shortstat;这样,重新审核所需的会话输出不会超过第一轮——而且由于这是一项已有审核轮次的任务,任务 ID 不成疑问,简报的步骤 2 可直接跳过。然后运行:
revmux --task <id> --run 02-after-fix --profile <picked> --no-tui \
> /tmp/revmux-<id>-02-after-fix.json 2> /tmp/revmux-<id>-02-after-fix.log
revmux 会自行注入先前的轮次。不要将先前的发现粘贴到 scope 中——这会重复注入内容,并让 Agent 围绕本应重新推导的结论展开。
作者推送更新后重新审核的拉取请求,必须先重新获取,并再次附上 --workdir:references/pr.md,步骤 6。
根据本轮修复涉及的内容选择审核配置,而不是根据轮次编号。 重新审核不一定范围更小:第一轮的发现可能通过重新设计得到修复,而范围更窄的审核名单无法捕捉其审核视角未覆盖区域中的回归。
| 修复内容 | 配置 |
|---|---|
| 在第一轮标记的范围内进行局部修改 | final——bugs+impl 是确认修复的审核形式,且不会报告 major 以下的问题 |
| 扩展到审核未覆盖过的测试或结构 | comprehensive——这确实是全新的范围 |
| 仅修改文档 | final——文档修复是新的文案,而 comprehensive 会审核这些内容 |
| 时间受限,仅关注正确性 | focused |
默认使用 final,并让 comprehensive 证明自己值得选择。 在已归档的轮次中,发现的问题大约有四分之三都是 minor,而 minor 问题从不收敛——无论 gating 数量如何变化,每轮都会出现大约三个,包括 gating 已连续两轮为零的轮次。final 不报告 major 以下的问题,而使用它进行的重新审查轮次在语料库中产生了最短的可操作清单。仅仅因为修复改动了测试文件,并不意味着出现了新的审查范围;上面的条目要求的是审查确实尚未覆盖的范围。 |
值得为第二轮失败投入一组审查人员的情况,是修复没有解决问题,或是在错误的位置解决了问题。这属于 impl 视角,而 focused 不包含它——因此,重新审查应缩小到 final,而不是 focused。
文档是重新审查不会扩展覆盖的唯一范围。其他所有遗漏都是上一轮未审查的代码;文档修复则是这轮刚写下的一个句子,对它运行 docs 视角,只会轮番找出关于这个句子的问题,而代码原地不动。
将其作为 AskUserQuestion 提出,并将匹配的选项放在首位并标记为 (Recommended)。
绝不要悄悄缩小审查范围:用户若没注意到,得到的审查就会比他以为自己要求的更小。用户自己指定了配置时,跳过提问。
归档整理
每轮都会保留每个 Agent 的原始输出流,因此归档每轮大约会增加半 MB,而 revmux 不会自行清理。每次会话检查一次,在展示最后一轮之后——绝不要在轮次之间检查;循环模式下,只能在循环退出后检查。
revmux stats
totals.size_mb 是整个归档的大小。低于 20MB 时,不必提及,继续处理:在一份关于发现的问题的报告中,磁盘空间无关紧要。超过该值时,建议将其缩减到大约 10MB。
建议按 last_run 从最早到最新排列,删除完整任务,且绝不删除本会话审查的任务。作出选择时,每个 tasks 条目都包含 id、description、rounds、size_mb 和 last_run。
完全没有 last_run 的任务并非最早,而是没有日期记录,将它排在首位会把从未完成轮次的任务排在已完成轮次的任务前面。将这些任务按 id 排在有日期记录的任务之后,并在选项中说明它们没有已完成的轮次。
在删除任何内容之前,只提出一个 AskUserQuestion:
- 将合计后可使归档低于约 10MB 的最早任务作为首个选项,并标记为
(Recommended) - 若首个选项并非仅包含该任务,则将最大的单个任务作为第二个选项
- 将保留所有内容作为最后一个选项
每个选项都要说明可释放多少 MB,并列出每个要删除任务的 description 和 rounds——只说“清理旧任务”或只列出 id、不说明审查内容的选项,都无法让用户权衡选择。
在问题本身说明会失去什么: 删除任务会连同其中的轮次一起删除,因此 revmux stats 和 self mode 的语料库都会相应减少这些证据。
然后严格按照用户的选择删除,每个任务调用一次:
revmux cleanup --task <id>
它会删除该任务并输出删除内容。这是 revmux 中唯一会删除内容的功能,而且只会删除指定的任务:路径、轮次名称或拼写错误都会导致操作报错且不删除任何内容;正在被运行中的审查使用的任务也会被拒绝删除。绝不要以其他方式删除任务目录,也绝不要扩大用户选择的范围。
调试看起来有问题的审查
归档位于 revmux new 报告的轮次目录中,与执行时针对的 input/ 同级:
| 问题 | 位置 |
|---|---|
| 为什么这个 Agent 什么都没报告? | agents/<name>.jsonl |
| Agent 是否卡住或重试过? | events.jsonl;<name>.retry.jsonl 表示它重试过 |
| 综合阶段是否丢掉了什么? | stages/1-found.json 与 2-synthesized.json |
| 验证阶段是否错误地驳回了内容? | stages/2-synthesized.json 与 3-verified.json |
| 这个 Agent 收到的提示是什么? | prompts/agents/<name>.md |
| 使用了哪段透镜文本,来自哪一层? | manifest.json |
Self 模式——根据记录调整配置
由 "revmux self"、"self-improve revmux"、"tune revmux" 触发。它不会审查任何内容:不会开启轮次,不会启动 Agent,也绝不会读取工作目录中的代码。它会读取以往轮次的产出,并提出对审查配置本身的修改建议。
你的任务是把结论和一项具体修改交给他,而不是提供仪表板。
步骤 S1:运行分析
${CLAUDE_SKILL_DIR}/scripts/analyze-corpus.py
它会遍历归档,打印带有对应数据编号的编号结论,然后列出支撑这些结论的表格。只读,不运行模型,可在审查期间安全运行。--tasks-dir 可指定其他目录;如果需要基于打印结果进行深入分析,--json 会提供完整测量数据。
不要手动重新推导这些内容。 该脚本编码了首次分析时容易出错的解读方式——验证看似没有作用,是因为重新分类不会显示在阶段的输入/输出中;不同执行器对 tokens 的含义不同;按 Agent 统计的轮次被重复计数。对归档临时使用 jq 会重现这些错误。
接下来,查看提案会修改什么,而不是审查做了什么:
revmux config > /tmp/revmux-config.json
务必以此为依据提出建议,绝不要以随附默认值为依据。 用户可能有自己的 comprehensive.md,而本技能从未见过其 Agent 名单。
步骤 S2:确定将修改写入何处
只能写入 ./.revmux/。 绝不能写入 ~/.config/revmux/,因为那是用户跨项目共用的配置层。也不能写入嵌入式目录,因为它位于二进制文件内——除非工作目录本身就是 revmux 仓库;透镜和配置档文本来自 app/prompt/defaults/,写入本地覆盖文件会使其偏离随附版本。
只把要修改的文件放在那里,而且必须等他说应用后再放。 提示文件逐个解析,因此 ./.revmux/ 中缺少的文件会在 revmux 升级时继续使用随附文本。revmux init 会写入所有已解析文件;为了修改其中一个文件而冻结整个目录树到当前版本,绝不要在此处运行它。
路径是该文件在提示目录树中的位置,例如 lenses/<name>.md 或 prompts/profiles/<name>.md。如果 ./.revmux/<path> 已存在,就地修改。否则,复制当前解析得到的文本——有用户层文件时复制用户层文件,否则复制嵌入式文件——然后修改副本:
f=lenses/adversarial.md # the file the change edits
if [ ! -e ".revmux/$f" ]; then
mkdir -p ".revmux/$(dirname "$f")"
if [ -e "$HOME/.config/revmux/$f" ]; then
cp "$HOME/.config/revmux/$f" ".revmux/$f"
else
d=$(mktemp -d /tmp/revmux-defaults.XXXXXX) && revmux --dump-defaults "$d" && cp "$d/$f" ".revmux/$f"
fi
fi
--dump-defaults 会跳过已存在的文件,因此每次都要将其写入一个新的空目录;重复使用的目录可能包含已被取代的文本。如果 .revmux/ 已纳入 git 跟踪,而他有所顾虑,请告诉他:git checkout 可以还原任何他后悔的修改。
步骤 S3:简要说明发现
先用自己的话概述脚本打印的结论,每项用 两到三句话。删掉用户无法据此采取行动的内容。不要粘贴表格——表格是给你看的,而用户问的是该怎么做,不是这些数字是什么。
只说明一次样本量:“24 轮,涵盖 7 项任务,全部基于此代码库”是用户可以权衡的限制条件。
只有在同时说明其歧义时,才引用各视角的数字。脚本的最后一张表和 revmux stats 中的 ambiguous 都会显示这一点:发现的视角由模型提供,而一个携带两个视角的 Agent 往往会同时提及二者。如果某个视角的歧义占其提出项的大多数,就不能单凭它提出建议——说明这一点,但不要提出建议。出于同样的原因,脚本按视角统计的降级项一栏已经缩小到只包含提及单个视角的发现。
如果脚本表示语料太少,就到此为止。 这就是有效的答案。与其凭四轮结果编造建议,不如说明目前还没有结论。
步骤 S4:提出一项改动
只提一项。选择支持最充分的改动,并说明:
- 改什么——按照步骤 S2 所述的
./.revmux/<path>,并具体说明如何修改 - 依据的数据——引用脚本打印的那项数字
- 如何判断是否奏效——如果奏效,下一轮中哪项测量值会发生变化
然后使用 AskUserQuestion:应用、跳过还是停止。根据用户的回答采取行动,然后以相同方式提出下一项。用户说停止或结论不再有内容时,就停止。绝不批量修改,也绝不未经询问就应用修改。
结论不一定要转化为改动。 有些结论值得了解,但不值得采取行动——说明这一点,然后继续,不要为了给每项结论配套而硬凑改动。
会话示例
User: "revmux this branch"
→ preflight.sh → all present
→ git branch --show-current + git status --short → on tui-rework, clean
→ git diff master...HEAD --shortstat → 22 files, +840/-310, handed to the brief as the scale
→ "Preparing the round for the branch against master…"
→ one subagent: reads the diff once, matches no existing task, derives `tui-rework`, opens
01-initial, writes task.md/scope/goal → returns round_dir, wrote[3]
→ revmux --task tui-rework --run 01-initial --no-tui > /tmp/…json (background)
→ tell user: ~9 min, tail -f /tmp/…log for live progress
→ Monitor on <round_dir>/events.jsonl, milestone kinds only; one folded line a minute, silence between
→ exit 1, sources 4/4, degraded []
→ 6 findings: 1 major, 5 minor; 2 corroborated across bugs+impl and adversarial
User: "fix the major one and run it again"
→ fix applied
→ "Preparing the round for the fixes…" → same subagent, handed task `tui-rework` and the fixes'
shortstat so it neither matches a task nor re-measures; opens 02-after-fix and writes its own
scope; the profile is the repo's and revmux resolves it
→ revmux --task tui-rework --run 02-after-fix --no-tui
→ exit 0, nothing above threshold
User: "revmux the branch, I want to watch it"
→ subagent prepares 01-initial and returns its paths
→ launch-revmux.sh --task tui-rework --run 01-initial > /tmp/…json (background)
→ agterm: split session, so a pane overlay on the agent's own pane, TUI live, self-closes 30s after the report
→ no Monitor and no status lines: he is watching it
→ same JSON, same exit code, Step 5 onward identical
User: "revmux pr 123"
→ preflight.sh → all present
→ gh repo view → umputun/revmux; gh pr view 123 → head `feature/oauth`, base master, +410/-95, 12 files
→ revmux config → .paths.tasks has no entry with that url; derive `pr-123`
→ git fetch origin pull/123/head:revmux-pr-123; git worktree add /tmp/revmux-pr-123 revmux-pr-123
→ merge-base origin/master revmux-pr-123 → 4ed3259
→ subagent opens pr-123/01-initial and writes task.md (url, branch, base), scope, goal, context
→ revmux --task pr-123 --run 01-initial --workdir /tmp/revmux-pr-123 --no-tui > /tmp/…json (background,
from the repo root — the archive belongs to it, not to the worktree)
→ exit 1, 4 findings; report them
→ git worktree remove /tmp/revmux-pr-123 --force; git branch -D revmux-pr-123
User: "what lenses does revmux have?"
→ no run; `revmux config`, report .lenses[] with descriptions
User: "revmux self"
→ analyze-corpus.py → 24 rounds over 7 tasks, five numbered conclusions
→ say three of them in a sentence each: verification demotes 21 and rejects 2, so it is a severity
corrector; adversarial rates 61% major+ and holds most of the attributable demotions; three quarters is minor
→ revmux config → the roster actually running
→ propose one: the adversarial lens's severity text, quoting the 61%, measurable by whether the
demotion count falls next round
→ AskUserQuestion: apply / skip / stop. Then the next one, or stop.