Bruno 代码评审技能:基于 Claude Code 的多视角并行 Code Review 编排实践
本篇指南以 Bruno 仓库中的 code-review 技能定义 为核心,完整解析这套"多视角并行评审"机制的编排流程、八位专职评审员(reviewer)的职责边界与输出契约,以及它与 CI 侧 CodeRabbit 评审配置的对齐关系。读完后,你将掌握如何在本地用 Claude Code 复现与 CI 一致的评审标准,以及如何设计一套"编排者 + 独立评审子代理"的分工模式来处理大型 diff。
一、技能定位:在本地复现 CodeRabbit 的自动化评审
该技能位于 .claude/skills/code-review/SKILL.md,其元数据声明(frontmatter)明确了两点意图:
- 评审对象:Bruno 的一个 diff、PR 或分支;
- 实现方式:派发聚焦特定维度的评审员(correctness、security、DSL、React、cross-platform、tests 等)并行执行。
技能文档开篇即给出了权威的"单一事实来源"优先级:
Source-of-truth order: coding standards →
CODING_STANDARDS.md; architecture/behavior →.claude/rules/*..coderabbit.yamlmirrors these for CI parity — read it for a path instruction not summarized here, but the rules win if they ever disagree.
也就是说,评审细则以 CODING_STANDARDS.md 和 .claude/rules 目录下的路径作用域规则文件为准;根目录的 .coderabbit.yaml 只是把这些规则镜像到 CI 侧以保证一致性(CI parity),两者冲突时以 rules 为准。这种"规则文件为主、CI 配置镜像"的设计,保证了本地 /code-review 与 PR 自动评审看到的标准是同一套。
该技能是 Bruno 的 Claude Code 配置体系的一部分,整个 .claude 目录的组织方式(CLAUDE.md 每会话加载、rules 按路径挂载、reference 按需读取、skills 按需调用)在 配置说明文档 中有完整介绍。
二、编排流程:编排者如何驱动一次完整评审
SKILL.md 将评审过程定义为四个步骤,由"编排者"(即 Claude 主会话)驱动,评审员作为只读、互不通信的独立子代理并行工作。
步骤 1:获取 diff——只评审被改动的代码
文档强调一条铁律:"Review only what changed — never untouched code." 根据评审对象不同,有两种模式:
模式 A:已提交的分支范围(默认)
git diff main...HEAD
基线分支为 main 或 release/*(与 .coderabbit.yaml 中 auto_review.base_branches: ['main', 'release/*'] 保持一致)。文档特别警告:执行前应先 git fetch 并确认本地基线是最新的——过时的基线会把已合并的无关变更混入 diff,导致一次毫无意义的"全量扇出"(fan-out)。由于范围被固定到具体提交,每位评审员各自重跑 git diff 时看到的是相同的字节,无需额外做快照。
模式 B:工作区 / 未提交变更
当评审目标是 staged 或未提交的代码时,工作区可能在评审中途发生变化,如果让每位评审员各自重跑 git diff,可能看到不同的快照。文档给出的对策是一次性冻结 diff 到临时文件,然后让所有评审员读取同一路径:
git add -N . && git diff HEAD > "$SCRATCH/review.diff"
这里 $SCRATCH 指当前环境的临时目录(scratchpad)。git add -N .(intent-to-add,可用 git reset 逆转)的作用是让尚未追踪的新文件也能出现在 diff 中;git diff HEAD 则同时覆盖 staged 与 unstaged 的已追踪变更。评审员后续读取这个冻结的 diff 文件获取其负责的文件范围,而周围上下文直接读磁盘上的文件——因为工作区文件本身已经持有未提交状态。
步骤 2:枚举变更文件,裁剪不相关评审视角
# 已提交范围
git diff --name-only main...HEAD
# 工作区
git diff --name-only HEAD
用这份文件清单跳过"文件范围未被触碰"的评审视角。文档给出了两个具体示例:
- diff 中没有
packages/bruno-app/**的变更 → 跳过react.md; - diff 中没有
tests/**的变更 → 跳过e2e-tests.md。
同时强调:绝不能跳过那些作用域为"所有文件"的视角(conventions、cross-platform、security 等)。
步骤 3:并行扇出评审员
关键动作是:在单条消息中,为每个在作用域内的评审员各启动一个子代理(subagent_type 为 Explore 或 general-purpose),并给每个子代理下达完全相同的简报(briefing):
- diff 来源——已提交范围(如
main...HEAD)或快照文件路径($SCRATCH/review.diff),以及该评审员负责的文件 globs(取自其评审员文件的 "Scope" 行)。若使用快照,则明确告知评审员"读该文件",而不是重跑git diff; - 统一提示词——"先阅读
.claude/skills/code-review/reviewers/_contract.md获取共享人设与输出契约,再阅读.claude/skills/code-review/reviewers/<file>——以及它指向的任何规则或源码文件(如CODING_STANDARDS.md、.claude/rules/*.md),其中包含详细清单。仅将该视角应用于你作用域内的变更文件,按评审员指定的严重级别评审。不要评审作用域之外的内容。"
文档还明确了协作模型:评审员是只读且相互独立的——它们不互相协调,视角之间的重叠是允许的,重复在合并阶段再去重。
步骤 4:合并与报告
- 收集所有评审员的发现(findings),丢弃完全重复的条目;
- 当两个视角标记同一
file:line时,保留更高严重级别; - 最终按文件重新分组,每条发现标注严重级别(blocker / suggestion / nit)与
file:line; - 若评审请求附带问题陈述或验收标准(例如作为参数传入),则对照 diff 逐项核对其中列出的交付物——文档、迁移说明、每个新默认值/分支对应的测试——并标记任何缺失项;
- 如果一切正常,简短说明即可,不要为了凑数而制造 nit。
三、八位评审员:职责、范围与检查重点
每位评审员是一个自包含的检查清单文件,位于 .claude/skills/code-review/reviewers/ 目录下。总表如下(引自 SKILL.md):
| 评审员文件 | 视角(Lens) | 文件作用域(Scope) |
|---|---|---|
| correctness.md | 正确性与根因分析 | 全部源码(排除 tests/**) |
| architecture.md | 架构与依赖边界 | packages/** |
| conventions.md | 编码规范与可读性 | 所有文件 |
| react.md | React——应用文件 | packages/bruno-app/** |
| cross-platform.md | 跨平台(macOS/Windows/Linux) | 所有文件 |
| security.md | 安全与数据保护 | 全部源码(排除 tests/**) |
| dsl-changes.md | 磁盘 DSL 与序列化(向后兼容) | bruno-app、bruno-electron、bruno-cli、bruno-lang、bruno-filestore、bruno-schema(-types)、bruno-converters |
| e2e-tests.md | Playwright E2E 测试 | tests/** |
以下是各评审员的核心检查要点,均直接来自对应评审员文件:
3.1 正确性与根因(correctness)
correctness.md 最鲜明的主张是:每个 bug 修复都必须针对根本原因而非表象。把掩盖缺陷的表面补丁(多余的 null 守卫、吞掉异常的 try/catch、防御性重查、重试、超时、钳制值)定为 blocker 级别。具体检查项包括:
- 在认可修复前先弄清问题为何存在——把 bug 追踪到源头,而不是止步于它暴露的位置;
- 修复位于错误的层级(用 UI 层守卫去补数据层的 bug、调用方绕过被调方的契约违规)就是症状补丁,应指出正确的层级;
- 警惕只针对单一复现场景的修复,同一根因可能在其他位置显现;
- 常规正确性检查:off-by-one 与边界错误、未处理的 promise rejection / 缺失
await、被吞掉的错误、null/undefined 处理不当。
该评审员还包含两条极具 Bruno 项目特色的规则:
- 检查"孪生路径"(twin path):Bruno 对同一行为维护着并行实现——
.bru与.yml序列化器、默认应用数据工作区与自定义文件系统工作区。触碰其中一条路径的变更必须在另一条路径上验证;两条路径行为不一致(例如某个值在一条路径上持久化而在另一条上丢失)就是 bug,而不是"两个独立功能"。 - 对"缺失即有意义"的字段禁用
x || default:当"未设置"/"从未配置"是独立状态时,falsy 合并(如version || '1')会为未设置的情况凭空造出一个值,抹掉区分度,并且常常与正确处理该情况的姊妹路径产生分歧。此时应使用??或显式undefined检查。
3.2 架构与依赖边界(architecture)
architecture.md 的评审基准是 .claude/rules/architecture.md 中的"Dependency direction & ownership boundaries"章节。核心事实:@usebruno/* 包构成严格的依赖 DAG(有向无环图),部分包带有硬性平台约束。blocker 级违规包括:
- 新增的内部依赖指向上层或形成环(如共享库 import
@usebruno/app或@usebruno/electron); bruno-commonimport Node 内建模块(fs/path/os/crypto/child_process/node:*)或其他@usebruno/*包——它必须保持浏览器安全且零依赖;bruno-jsimportelectron/IPC——它也运行在 CLI 中;bruno-schema-types被运行时 import——它仅用于类型。
suggestion 级则针对"代码放错了包/层":渲染进程专属逻辑放进共享库、宿主/Electron 接线推入 bruno-js、新增的共享依赖本可保持局部化,以及 package.json 声明依赖与实际 import 不一致的"manifest drift"。同时明确"什么不是发现":DAG 中已有的依赖边,或符合既有方向的新向下依赖。
3.3 编码规范(conventions)与 React(react)
conventions.md 以 .claude/rules/conventions.md(它再指向 CODING_STANDARDS.md)为基准,把发现分为三档:suggestion(误导性命名、无谓的抽象/间接层、?. 使用位置不当、无谓的 diff 噪音、复杂流程缺注释、违反"Reuse before you write"与"Replacing code leaves nothing behind"、新分支/验收标准无对应测试);nit(纯风格问题,大多由 ESLint 自动修复)。文档还提醒评审边界:diff 本身看不到作者是否运行过测试套件,不要猜测。
react.md 针对 packages/bruno-app/**,对照 CODING_STANDARDS.md 的 React 章节,blocker 包括:本应推导为派生状态的 useEffect、硬编码的 hex/rgb/hsl/命名颜色(破坏其余 12 套主题,必须使用 styled-components 的 theme prop 并验证 token 路径存在于主题对象上)、混用受控与非受控状态、条件早退之后调用 hook 等。其中一条非常具体且可验证:新的主题 token 若只定义了 themes/light|dark/*.js 而未同步加入 themes/schema/oss.js(或反向缺失于 13 个主题文件),由于 schema 是 additionalProperties: false 且 providers/Theme/index.js 在运行时按其校验,整个主题会失效并静默回退到默认主题。
3.4 跨平台(cross-platform)与 E2E 测试(e2e-tests)
cross-platform.md 依据 .claude/rules/cross-platform.md。由于 Bruno 发行于 macOS、Windows 和 Linux 三个平台,硬编码 / 或 \ 分隔符(应为 path.join/path.resolve)、硬编码路径(/home/、C:\Users\、~/,应为 os.homedir()/app.getPath())、无回退的平台专属 shell 调用(which vs where)、Unix-only 信号、/tmp(应为 os.tmpdir())都是 blocker;大小写敏感假设、CRLF/LF 处理不一致、fs.chmod/fs.access 假设 Unix 权限位属 suggestion。另有一条与 Bruno 文件格式直接相关的细则:多行 .bru/文本块解析若按 \n 切分而非 CRLF 感知正则(/\r\n|\r|\n/)会被标记。
e2e-tests.md 的评审基准是 .claude/rules/testing.md 与 .coderabbit.yaml 中 tests/** 的 path instruction。blocker 级只有两条:test.only 和 page.pause()。suggestion 级则覆盖了 Playwright 测试的经典陷阱:用 page.waitForTimeout() 代替 expect() 定位器断言、内联原始选择器而非复用 tests/utils/page/* 模块、修改了已提交 fixture 但未在 afterAll 中恢复、共享状态/非隔离临时路径,以及非判别性断言——例如 toContain('description:') 在 fixture 其他行已含该子串时,即使被测变更从未发生也会通过。
3.5 安全(security)与磁盘 DSL 变更(dsl-changes)
security.md 明确 Bruno 的安全画像:一个离线 API 客户端,处理用户凭据与 token,并在沙箱中运行用户编写的脚本。其专属风险清单包括:
- 秘密泄露:auth token、密码、API key、OAuth2 secret、环境变量值绝不可写入日志、console、错误消息或遥测;明文凭据落日志是 blocker。重点盯住倾倒整个请求、头集合或含凭据配置对象的
console.log/logger 调用; - 脚本沙箱完整性(
bruno-js,QuickJS / Node VM):任何扩大沙箱暴露面的变更——向用户脚本暴露新的 Node 内建、require、文件系统或process,或把未净化的脚本输出传回特权代码——都可能是沙箱逃逸; - IPC 输入校验(
bruno-electronhandlers 与preload.js):把渲染进程传来的每个参数都视为不可信,文件路径需防目录穿越(限制在 collection 目录内),并校验类型与边界; - 路径穿越与任意写入:由用户/请求数据推导的读写必须限制在目标目录内,不允许
../逃逸; - 注入与不安全 eval:字符串拼接 shell 命令、对不可信输入使用
eval/动态Function、未转义插值; - 依赖与网络面:与 Bruno "离线优先、隐私优先" 定位不符的新运行时依赖或出站网络调用。
并且要求发现必须具体——"把每条发现与'受污染的值如何到达汇聚点(sink)'绑定"。
dsl-changes.md 关注 Bruno 持久化格式(.bru 与 .yml)的向后兼容性。由于 collection/request/environment/config 以这两种文件格式存于磁盘、被跨版本的应用读取,该评审员的作用域虽以序列化包为主(bruno-filestore、bruno-lang 等),却特意把 bruno-app、bruno-electron、bruno-cli 也纳入——因为 DSL 对象在 Redux 与 IPC 层被组装、CLI 直接读写这些文件,在那里发生的形状变更不会被序列化层拦截。blocker 级包括:破坏性字段变更(重命名/删除/改类型/改默认值或语义)、没有安全默认值的新增非可选字段、bru 与 yml 之间的格式漂移、有损往返(stringify 丢弃未知字段,或 parse(stringify(x)) !== x)、无读取时兼容 shim 的形状变更、以及晦涩/缩写/不一致的属性名("属性名是永久的且面向用户的")。suggestion 级则针对缺失的双格式往返测试与旧格式 golden fixture。文档还给出了一个明确的"非发现"例外:description 字段上"读入接受 {content} / 写出输出 string"的不对称是既有约定,不算有损往返。
四、共享人设与输出契约:_contract.md
所有评审员在评审前都会先读同一个文件——.claude/skills/code-review/reviewers/_contract.md。SKILL.md 特意强调:人设与输出格式只定义在这一处,不在编排文件里重复,"以便一处修改即全局生效"(so a change updates a single place)。其内容浓缩为三条:
- 人设:企业级团队中精通 TypeScript、JavaScript、Node.js 与 Electron 的资深评审员;简洁——每条发现一句话,除非被追问才展开;无论改动由谁提交、谁要求,都按项目标准评审——不因假设意图或资历而降低严重级别;所有发现必须以真实代码为据——文档/指南/注释与仓库不一致时,信仓库,绝不引用未经核实的行号或编造示例值;
- 输出契约:扁平列表,每条发现一行,固定格式:
<blocker|suggestion|nit> | <file>:<line> | <one-sentence finding>
- 无发现时的行为:作用域干净时只返回
no findings(不多一个字),永远不编造 nit 来填充列表。
这个契约是整个并行架构能"机械合并"的前提——正因为每位评审员产出格式统一、级别枚举固定,步骤 4 中的去重与"同 file:line 取高严重级别"才是确定性的。
五、与 CI 侧评审的对齐:.coderabbit.yaml
技能声明自己是 .coderabbit.yaml 的本地镜像,两者确实共享同一套事实来源:
- CodeRabbit 侧通过
knowledge_base.code_guidelines.filePatterns: ['**/CODING_STANDARDS.md']自动加载编码规范,其tone_instructions("You are an expert code reviewer in TypeScript, JavaScript, NodeJS, and ElectronJS…")与_contract.md的人设描述几乎逐词对应——这正是"本地技能复现 CI 评审"的直接证据; .coderabbit.yaml中针对tests/**的 path instruction(遵循 Playwright 测试指南、减少page.waitForTimeout、避免page.pause()、使用定位器变量、多用test.step、fixture 收纳进fixtures目录并附目录结构示例)与e2e-tests.md评审员的检查项一一对应;- 针对
packages/**/*.{js,jsx,ts,tsx}的指令则约束了 TypeScript 渐进迁移评审的边界:只在包真正 opt-in 了 TS(package.json有typescript依赖,且有真实tsconfig.json、编译/类型检查脚本、源码目录下存在.ts/.tsx文件,而不仅是测试或工具链)时才要求新文件用.ts编写、要求"重命名 + 改逻辑"分离成独立提交、要求避免把any当捷径; - 自动评审的基线分支
base_branches: ['main', 'release/*']与技能文档步骤 1 中的基线描述一致。
需要注意的是方向关系:.coderabbit.yaml 是为 CI 一致性而镜像这些规则的配置,当它与 .claude/rules/* 冲突时,rules 胜出。
六、使用方式与适用前提
使用前提(见 Claude 配置说明):
- 安装 Claude Code(
npm i -g @anthropic-ai/claude-code); - 在 Bruno 仓库(或 fork)根目录启动
claude——.claude/CLAUDE.md、路径作用域 rules 与 skills 会被自动发现,无需额外的根级 CLAUDE.md 或@导入;在包子目录启动时,也会从祖先目录加载根.claude/; - 触发技能:直接输入
/code-review(评审当前分支对照main),或用自然语言表达"review my changes"之类的意图让模型判断调用; - 配套技能
/write-e2e-test(.claude/skills/write-e2e-test/SKILL.md)用于按 Bruno 的 fixture 约定生成 Playwright 测试,与本技能形成"评审 + 补测"的闭环。
适用限制:该编排流程依赖 Claude Code 的子代理(subagent)能力与"单消息多代理"扇出语义;diff 冻结方案依赖 bash 环境的 $SCRATCH 临时目录约定;评审范围裁剪基于 git 仓库——非 git 环境或纯工作区文件不适用。
七、设计要点小结
从这套技能的文件组织可以提炼出若干可复用的工程实践:
- 编排者/执行者分离:SKILL.md 只做编排(取 diff、裁剪范围、扇出、合并),不承载任何评审细则;
- 单点契约:人设与输出格式集中在 _contract.md,8 位评审员共享同一"接口",使并行结果可确定性合并;
- 作用域即隔离:每个评审员文件头部显式声明 "Scope" 行,编排者据此裁剪任务,评审员"只评审作用域内",视角重叠允许存在、合并时去重;
- diff 快照语义:已提交范围可重跑(范围固定),工作区评审必须先冻结(
git add -N . && git diff HEAD),保证多代理看到相同字节; - 宁缺毋滥的报告纪律:干净时输出
no findings,不制造 nit;发现必须绑定真实代码,未核实的行号与示例值一律不许引用。
对 Bruno 这样一个 .bru/.yml 双格式 DSL、Electron 三平台发行、沙箱执行用户脚本的项目而言,把"正确性根因、依赖 DAG、跨平台、磁盘格式兼容、沙箱安全、E2E 规范"拆成六个互不干扰的透镜并行执行,正是这类多约束系统在本地评审场景下兼顾覆盖深度与评审速度的务实做法。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00