Skip to main content
Glama
README.md
# cc-code-review

[![CI](https://github.com/KentQi/cc-code-review/actions/workflows/ci.yml/badge.svg)](https://github.com/KentQi/cc-code-review/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/KentQi/cc-code-review?label=release)](https://github.com/KentQi/cc-code-review/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
![Node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen)

[English](README.en.md) | 简体中文 · [CHANGELOG](CHANGELOG.md) · [文档索引](docs/README.md) · [Releases](https://github.com/KentQi/cc-code-review/releases)

**EN**: An MCP server that calls Claude Code's own `/code-review` (low..high, including `--fix`) or runs a fleet pipeline with the same three-phase structure claude's playbook uses (8/10 parallel finder subagents, one verifier per surviving candidate, one sweep finder; findings ≥ 75 of 0–100). Reviews — PR / branch / working dir / last commit, effort `low→max` — from ZCode or any MCP client, full report back. Works with a CC-Switch-style local gateway, direct OAuth login, or an API key, on Windows and macOS, with the standalone CLI or the desktop-app-embedded CLI. Nothing is installed into Claude Code. Ships as an Agent Plugins v1.0.0 package (`plugin.json` + `skills/` + `mcp.json`), so conforming harnesses can load the skill and the MCP server from one directory.

把 **Claude Code 的 code review 能力**封装成 MCP server,让 ZCode(或任何 MCP 客户端)直接调用:
ZCode → MCP → `claude -p`(headless)→ claude 内置 /code-review(low..high,含 --fix)或内联舰队流水线(max:10 finder + 每候选 verifier + sweep)→ 审查报告原样返回给 ZCode → ZCode 根据报告改代码 → 再次调用复核。

```
ZCode ──MCP(stdio)──▶ cc-code-review ──spawn──▶ claude -p --output-format stream-json
                            │                              │  cwd = 目标仓库
                            │                              └─ 内置 /code-review(默认)或内联舰队提示词(不写任何文件)
                            ◀── result(报告 + session_id + 花费) ──┘
```

## 前置条件

- Node ≥ **20**(CI 矩阵 20/22/24;Node 18 已 EOL,不再声明支持)
- 已安装 Claude Code。**无需 OAuth 登录**,三种接入方式均可:
  - **CC Switch / claude-code-router 等网关**:配置写在 `~/.claude/settings.json` 的 `env` 块,claude 每次启动自行读取(同名键 settings 覆盖进程环境变量),所以切换供应商对下一次 review 立即生效。本 MCP 会对**本地回环网关**(127.0.0.1 等)做 TCP 预检,网关挂了会立刻报"gateway not reachable"而不是干等到超时;本地网关流量不会走 HTTP 代理(自动补 `NO_PROXY`)。
  - **官方账号登录(OAuth/Pro/Max)**:首次需在终端交互式跑一次 `claude` 完成登录,之后 headless 直接可用。注意:若 MCP 宿主以服务/计划任务方式运行且读不到当前用户凭证(Windows Credential Manager / macOS Keychain),会报 "Not logged in"。
  - **ANTHROPIC_API_KEY 环境变量**:从宿主环境继承,天然可用。
- PR 范围审查需要 `gh` 已登录;本地 diff 审查(working-dir / branch / last-commit)只需 git。

## `claude` 可执行文件解析(跨平台)

本 MCP 不安装 Claude Code,只调用本机已有的 `claude`。它按以下优先级解析(`claude_review_env` 可查看命中来源):

| 优先级 | 来源 | Windows 路径 | macOS 路径 |
|---|---|---|---|
| 1 | `CLAUDE_CODE_PATH` 环境变量 | 任意可执行文件或 cli.js(不接受 .cmd/.bat/.ps1 shim) | 同左 |
| 2 | PATH 扫描 | `claude.exe` | `claude`(可执行) |
| 3 | 常见安装位置 | `%USERPROFILE%\.local\bin\claude.exe`(native installer)、winget Links、npm 默认前缀包内 exe | `~/.local/bin/claude`(native)、`/opt/homebrew/bin/claude`、`/usr/local/bin/claude`、`~/.claude/local/claude` |
| 4 | npm 全局 root(尊重自定义 prefix) | `<npm root -g>/@anthropic-ai/claude-code/bin/claude.exe` 或 `cli.js` | 同左 |
| 5 | Claude 桌面 app 内置 CLI(兜底,非官方入口) | `%APPDATA%\Claude\claude-code\<版本>\claude.exe` | `~/Library/Application Support/Claude/claude-code/<版本>/claude.app/Contents/MacOS/claude` |

说明:
- 不用 `where`/`which` 子进程(中文 Windows 上 `where` 输出 GBK 会解成乱码),直接用 Node 读 PATH 逐目录探测。
- **只装桌面 app、没装 CLI 也能用**:兜底到 app 内嵌的版本化 CLI(取最新版本目录;app 更新后路径可能漂移,建议正式使用装一份 CLI)。
- macOS 注意:若宿主从 Dock/Finder 启动,继承的是 launchd 的精简 PATH,本 MCP 会自动把 `/opt/homebrew/bin`、`/usr/local/bin`、`~/.local/bin` 等**追加**进 claude 子进程的 PATH(npm shim 的 `#!/usr/bin/env node` 与 claude 内部的 git/gh 都依赖它;追加而非前置,是为了不遮蔽用户自己的 PATH 优先级)。同理,ZCode 的 MCP 配置里 `command` 建议写 node 的**绝对路径**。
- macOS native 二进制首次 headless 运行若被 Gatekeeper 拦截(SIGKILL),错误信息会提示先在终端跑一次 `claude --version`。
- 超时/取消会杀整棵进程树(Windows `taskkill /T /F`,Unix 进程组 SIGKILL);MCP server 自身退出时也会清理存活子进程。

## 注册到 MCP 客户端(ZCode / Claude Code / Cursor / 其他)

### ZCode(一条命令)

**推荐:一条命令自动配置(v1.7.0 起)**。克隆本仓库后,在里面跑:

```bash
npm install
npm run init
```

`init` 会做一轮**免费自检**(claude CLI 解析与版本、加固 flag 地板、shell guard 实测、skill 状态、git/gh、网关连通性),然后**按本机环境**把注册写进 `~/.zcode/cli/config.json`——node 用的是当前运行的绝对路径、入口文件用本仓库的绝对路径、网关凭据(如果检测到 CC Switch / settings.json / 环境变量配置)自动带上。Windows/macOS/Linux 无需手改任何路径。写完重启 ZCode(或重连 MCP)即可,工具名:`mcp__cc-code-review__claude_code_review` 等。

> ⚠️ **升级代码后必须重启 MCP 宿主。** stdio MCP 是常驻进程,模块在启动那一刻就载入了内存;而 shell guard 的 hook 脚本是**每次 Bash 调用时从磁盘新起的**。所以只更新文件不重启,会得到"旧服务器 + 新 hook"的错配:两者对策略的判断不一致,guard 自检会(正确地)拒绝运行,报 `the shell guard is installed but did not behave as expected`。这不是 bug,是 fail-closed。`claude_review_env` 会直接告诉你答案:首行 `cc-code-review server: vX.Y.Z`,若磁盘源码比本进程新,会追加 `⚠ STALE PROCESS` 并给出重启提示(structuredContent 里是 `server.stale`)。

### Claude Code / Cursor / 其他 MCP 客户端(手动)

`npm run init` 只写 ZCode 的配置;其他客户端用下面这段标准 stdio 配置即可——注意 `command` 必须是本机 node 的**绝对路径**,`args` 必须是本仓库 `src/index.js` 的**绝对路径**(宿主的工作目录不可控,相对路径会解析到别处):

```json
{
  "mcpServers": {
    "cc-code-review": {
      "command": "<本机 node 绝对路径>",
      "args": ["<本仓库>/src/index.js"]
    }
  }
}
```

- **Claude Code**:放进项目 `.mcp.json`(或用户级 MCP 配置),或直接 `claude mcp add cc-code-review <本机 node 绝对路径> <本仓库>/src/index.js`。
- **Cursor**:放进 `~/.cursor/mcp.json` 或项目的 `.cursor/mcp.json`。
- **ZCode**:不想跑 `npm run init` 时,等价的手写位置是 `~/.zcode/cli/config.json` 的 `mcp.servers.cc-code-review`(字段同上)。
- **其他客户端**:任何支持 stdio MCP server 的宿主都接受这个形状;如果它同时支持 [Agent Plugins](https://agent-plugins.org/specification),也可以直接把本仓库当插件加载(见下一节)。

> 工具名(`claude_code_review` / `claude_review_status` / `claude_review_followup` / `claude_review_env`)与环境变量前缀(`CLAUDE_REVIEW_*`)**保持稳定**,不随 server 名改变——已注册过的调用方与脚本无需跟着改,改的只是 server 键名(工具前缀从 `mcp__claude-review__*` 变成 `mcp__cc-code-review__*`)。

## 作为 Agent Plugin / Agent Skill 使用(任意 harness)

本仓库同时是一个 **[Agent Plugins](https://agent-plugins.org/specification) v1.0.0** 包——仓库根目录就是 plugin root,固定位置放三个可移植文件:

```text
cc-code-review/
├── plugin.json                    # 插件清单(closed schema:名称/版本/描述/许可证)
├── mcp.json                       # stdio MCP server 配置(固定位置,不内联在 plugin.json)
├── skills/code-review/SKILL.md    # 可移植 Agent Skill(Agent Skills 格式)
└── src/ scripts/ ...              # MCP server 实现
```

- **支持 Agent Plugins 的客户端**:把本仓库目录作为 plugin 加载即可——`skills/` 下的 code-review skill 与 `mcp.json` 里的 stdio server 会被自动发现。前提是先在插件目录跑过 `npm install`(server 依赖在 node_modules;缺依赖时只有 MCP server 组件失败、skill 仍可加载——协议要求组件失败非致命)。
- **只支持 Agent Skills 的 harness**:把 `skills/code-review/` 整个目录复制进该 harness 的 skills 目录(例如 Claude Code 的 `~/.claude/skills/code-review/`,或项目级 skills 目录)。skill 正文与 harness 无关:有 subagent/并行工具就并行派发 finder,没有就顺序执行全部角度,不会因为缺少 fan-out 而跳过角度;报告格式、评分与过滤规则不变。
- **用 MCP server 时不需要装 skill**:fleet 流水线会把同一份 SKILL.md 正文内联进 prompt(见上文「评审流水线」),默认路径不往 `~/.claude` 写任何文件。
- **格式校验**:skill 遵循 [Agent Skills 规范](https://agentskills.io/specification)(frontmatter `name`/`description`、正文 < 500 行),插件清单遵循 Agent Plugins 1.0.0 的 closed schema;`npm test` 的 `plugin-test` 套件离线校验 `plugin.json` / `mcp.json` / `SKILL.md` 三个文件。

> 为什么单独测这三个文件:manifest 违规会让合规客户端**拒绝整个插件**,而 SKILL.md 违规只会静默丢弃 skill——两种失败都发生在 Claude Code 路径之外,其他套件看不见。

## 工具

### 1. `claude_code_review` — 跑一轮 review

| 参数 | 默认 | 范围 | 说明 |
|---|---|---|---|
| `repoPath` | 必填 | - | 被审查仓库的绝对路径(支持 `~/` 前缀) |
| `scope` | `auto` | 5 选 1 | `auto` / `pr` / `branch` / `working-dir` / `last-commit`;auto 按 PR → 领先默认分支的提交 → 未提交改动 → 上一条提交 自动判断 |
| `pr` | - | - | GitHub PR 编号或 URL |
| `baseRef` | - | 纯 ref 字符 | `branch` 范围的基线(缺省自动探测默认分支;**非法字符/修订范围会在评审前被拒绝**,见下文) |
| `focus` | - | - | 额外关注点(仅作为评审提示,不会被当作指令执行;**必须单行**) |
| `outputLanguage` | `中文` | - | 报告语言 |
| `postToPR` | `false` | - | 额外用 `gh pr comment` 把摘要贴回 PR(默认只返回报告,不外发)。**必须同时给 `pr`**:没有具名 PR 时预检直接拒绝(否则会带着一个无处可写的 GitHub 写权限,且提示词会要求模型猜一个 PR 号) |
| `fix` | `false` | - | **修复模式**:报告后直接修改仓库文件修复 findings(只放开 Edit/Write,**无任何 git 写命令**,不 commit;`.git/**`、`.env*`、密钥文件均在 deny 列表,实测拦截有效) |
| `effort` | `max` | low/medium/high/xhigh/max | **模型推理力度**,与 Claude Code 原生 `--effort` 同档位;默认 max(最深、最慢、最贵),想快想省就显式传 `low`/`medium`;CLI 不支持时自动退化为 think/ultrathink 关键词(两者不会叠加) |
| `reviewLevel` | 由 `effort` 派生 | low/medium/high/max | **评审自身深度**(同原生 `/code-review low\|high\|max` 的参数)。舰队结构与原生 playbook 同为三阶段(finder → 每存活候选 1 个 verifier → sweep),数字由 `src/prompt.js` 的 `FLEET_NUMBERS` 单一来源渲染(工具 schema、prompt、本表同一来源;随包 SKILL.md 不再复述数字,改为指向 run 的 "Reviewer fleet" 行):low = 单遍无子代理(≤4 findings);medium/high = **8 个 finder**("3+5" 形态:5 个 brief + 3 个拆分 finder,× 6 候选,findings 上限 8);max = **10 个 finder**("5+5" 形态:每个 brief 拆两次,× 8 候选)+ **每个存活候选 1 个 verifier**(CONFIRMED/PLAUSIBLE/REFUTED)+ **1 个 sweep finder**,findings 上限 15;进入验证的候选上限 20。max 的总代理数是结构性的 15+(实测 17),不是硬编码数字 |
| `ultracode` | `false` | - | **Ultracode 模式**:通过 `--settings` 写入 `ultracode: true`,启用原生"常驻动态工作流编排"(每个实质任务走 Workflow 工具、多代理、不计 token 成本);与 effort 正交;旧版 CLI 忽略该键(结果里的 `workflow_runs` 显示是否真正生效) |
| `model` | - | - | 覆盖模型(`--model` 透传) |
| `maxTurns` | `80` | 4–200 | agentic 轮数上限 |
| `timeoutMinutes` | `30` | 1–60 | 硬超时——不设费用上限时它是唯一的总闸;触发时已完成的子代理报告照样返回 |
| `maxBudgetUsd` | `0`(不设上限) | 0–50 | API 花费上限(`--max-budget-usd`)。**默认 0 = 任何 effort 级别都不设费用上限**:上限会在父代理综合前截停运行,而每个完成的子代理报告都已增量落盘、429 打断的子代理会从 output_file 转录抢救,设上限只会白扔已付费的工作;兜底 guards 是 `timeoutMinutes` 与 `maxTurns`。仍可显式设值;env `CLAUDE_REVIEW_MAX_BUDGET_USD` 只改默认值,显式传参优先 |
| `pipeline` | `auto` | auto / native / fleet / skill | 评审引擎。**auto**:level 为 low..high(含 fix——走命令自带 `--fix`)时直接用 claude 内置的 `/code-review`(无需任何文件);max(以及映射到 max 的 effort=xhigh)、scope=last-commit 无父提交、或存在同名 skill 遮蔽内置命令时改用本 MCP 的舰队流水线(理由见下)。**native** 强制内置命令;**fleet** 强制内联舰队(指令写在 prompt 里);**skill** 为兼容旧行为(唯一会往 `~/.claude` 写文件的模式) |
| `installSkillIfMissing` | `false` | - | 仅在 `pipeline=skill` 时有意义:skill **缺失**时安装(默认不装——缺失时退化走内联舰队流水线;已存在且由本 MCP 管理的副本在实际使用前自动刷新;带本 MCP marker 的副本会在使用前自动刷新(对其本地改动会被覆盖);无 marker 的用户副本永不写入、永不被删除;uninstall 只删本 MCP 写的文件) |

返回(默认异步:先返回 jobId(<1 秒)与预检结论,报告用 claude_review_status(jobId) 取回;waitForResult: true 时为下列同步返回):markdown 报告(`## Code Review Summary` / `## Findings (N)` / `## Notes`,与随包 SKILL.md 同一份格式合同)+ 每个评审子代理各自的报告(`## Reviewer subagent reports`,数量随 diff 规模而定)+ 元数据(`session_id`、花费、轮数、日志路径、模型溯源)。claude 的认证/网关类失败会以醒目的 `⚠️ REVIEW FAILED` 横幅返回并附处理建议,不会伪装成"报告"。

### 2. `claude_review_status` — 轮询/获取/取消后台 review 任务(免费)

`claude_code_review` 默认**异步**:免费预检全部通过后立刻返回 jobId(<1 秒)——因为真实 review 要 2-15 分钟,而多数 MCP 客户端 ~30 秒就掐断工具调用。本工具:传 jobId 轮询(运行中报已耗时)或取回完整报告;传 jobId + cancel=true 中止并停止计费;不传参数列出本进程的任务。需要同步语义的客户端在 claude_code_review 里传 waitForResult: true。

### 3. `claude_review_followup` — 追问 / 复核

拿着上一次返回的 `session_id` 继续对话(`claude --resume`)。

### 4. `claude_review_env` — 环境自检(免费;**不是只读工具**)

CLI 解析结果与来源、版本、`--effort`/`--max-budget-usd` 支持情况、skill 状态、网关连通性(回环网关 TCP 预检)、宿主环境中残留的认证变量告警、`git`/`gh` 可用性、`CLAUDE_CODE_EFFORT_LEVEL` 覆盖告警。首行输出机器可读的 `verdict: OK|NOT_FOUND|MISCONFIGURED`。`installSkill: true` 可手动(重)装 skill。

## 评审流水线:默认用 claude 内置的 `/code-review`,不写任何文件

Claude Code 2.1.x **自带** `code-review` 技能(`/code-review [level] [target]`)。本 MCP 默认直接调它,**不再往 `~/.claude` 装任何东西**。

| level | 默认走哪条 | 原因(实测 2.1.311) |
|---|---|---|
| low / medium / high | **内置 `/code-review`** | 单上下文、按"角度"逐个精读,10 秒级完成,零安装;fix 模式走命令自带的 `--fix`(二进制实测) |
| xhigh(映射到 max) | **本 MCP 舰队** | 与 max 同一条舰队流水线(xhigh 只是把 reviewLevel 映射到 max) |
| max | **内联舰队流水线** | 内置 max 会派子代理,而子代理爱写 `cd X && ls && git config …` 这类复合/越界命令,被 manual 模式成片拒绝(实测 100 次 denial、5 分钟不收敛);舰队提示词则明确约束在只读面内 |
| `last-commit` 且 HEAD 无父提交 | 内联舰队流水线 | 内置命令的 target 是**基线 ref**(`HEAD~1`),root commit 或空仓库没有父提交 |

- **内联舰队 = 不装文件**:`skills/code-review/SKILL.md` 的正文直接拼进 prompt(`## Pipeline (bundled with this MCP)`),模型照它执行;代价是每次 fleet review 多约 12KB 输入。
- **`pipeline` 参数**可显式指定:`auto`(默认)/ `native` / `fleet` / `skill`。`skill` 是唯一会写 `~/.claude` 的兼容模式,默认关闭。
- **同名 skill 会遮挡内置命令**(实测:装了 `~/.claude/skills/code-review` 后,`/code-review` 变成 `Skill` 调用)。因此存在同名 skill 时 `auto` 自动改走 fleet 并在结果里说明;显式 `pipeline=native` 会直接拒绝,而不是悄悄跑成别的东西。之前装过的那份可用 `claude_review_env { uninstallSkill: true }` 移除。
- 结果元数据带 `pipeline`(描述)与 `pipelineMode`(解析结果),`subagent_stats` 显示舰队规模。

## 安全模型(多轮独立审计后)

**进程边界(v1.3.0 新增,实测验证)**

- `--setting-sources user`:被审仓库可能自带 `.claude/settings.json`(project/local 作用域),加载它会执行仓库控制的 SessionStart hook(任意代码执行)并覆盖 `ANTHROPIC_*`(凭据外带)。本 MCP 只加载用户级 settings。已用对照实验验证:带恶意 settings.json 的仓库在不加该旗标时 hook 确实执行、API 连接被劫持,加上后两者都消失。
- `--permission-mode manual` + `--permission-prompts none`:allowlist 成为**硬边界**——不在名单内的调用直接拒绝,不再经过模型驱动的权限分类器(提示注入可以绕过分类器)。`--permission-prompts none` 让"本该弹窗询问"的调用也直接拒绝(headless 下没有宿主能回答弹窗)。代价是名单必须完整,实测中已补齐 `Skill`、`TodoWrite`、`Bash(git remote show:*)` 等评审管线必需项。

**工具权限**

- 评审模式**只读**:文件读取 + git/gh 只读命令 + subagent + skill 加载。**无** `gh api`(前缀规则无法区分 HTTP method,等于全量 GitHub 写权限)、**无** WebFetch(避免代码外带/SSRF)。
- 评审模式另有 deny 纵深:Edit/Write/NotebookEdit、git 写命令(add/commit/push/reset/checkout/config…)、`git diff --output=`(前缀规则 `git diff:*` 会顺带匹配到的写文件原语)、gh 写命令、以及 `node -e`/`python -c`/`sed -i`/`cat > f`/`tee` 等**能用 shell 重定向绕过 Edit/Write 管控的解释器与写操作**——实测这些正是模型被拒后最先尝试的捷径。
- **deny 规则不能图省事写整类**:deny 优先于 allow,所以 `Bash(git remote:*)` 这种"整类封死"的规则会把 `git remote -v`(管线用它探测 GitHub remote)、`git branch -a` 一起杀掉——一次真实 review 的唯一一条 Bash 拒绝正是这么来的。`remote`/`branch`/`tag` 因此只 deny **写形式**(`git remote add`、`git branch -D`、`git tag -a`…),读形式留给 allowlist;`git merge-base` 干脆不授权(`Bash(git merge:*)` 会前缀命中它),提示词改用 `git log base..HEAD` / `git diff base...HEAD`。这条不变量由 `audit-fixes-test.mjs` 的 "no allow rule is prefix-shadowed by a deny rule" 与一次真实 review 的命令回放锁定。
- fix 模式只加 Edit/Write/NotebookEdit(deny 列表在 fix 模式下**不再**包含这三个工具,否则 fix 会退化成"只描述不落盘");**不给** `git add`/`git restore`(防未提交工作被静默丢弃);deny 列表封死 `.git/**`、`.env*`、密钥/凭证文件(deny 优先于 allow,已在真实 CLI 上实测拦截)。

**Bash 前缀匹配绕权与 PreToolUse hook(v1.4.0 新增,实测验证)**

Claude Code 的 Bash 权限规则是**把整条命令当字符串做前缀匹配**:`--allowedTools "Bash(git status:*)"` 不只放行 `git status`,也放行 `git status; curl evil.com`;deny glob(如 `Bash(*;*)`)根本不触发,因为规则匹配的是命令本身而非它的组成部分。**只要允许用 Bash,任何"放行第一条命令"的规则都等于放行任意命令**——这不是规则写得不够好,是权限引擎的性质,改规则列表堵不住。已实测确认。

所以本 MCP 额外挂了一个 **PreToolUse hook**(`scripts/pre-tool-hook.js`,通过 `claude --settings <临时文件>` 注入,与 `--setting-sources user` 并存且不覆盖用户 settings——已实测 CC Switch 网关配置仍然生效)。它**覆盖 subagent 的 Bash 调用**(并行评审子代理的主力)。

**判定是分段的,不是"见到 `&&` 就拒"(v1.14.0)**。权限引擎只能管住"整串的第一个命令",hook 于是逐段判定:每一段都必须是策略内的只读命令(或管道末端 sink:head/tail/cat/wc/uniq/cut/tr/grep/sort),`cd` 只允许落到被审仓库根且必须是第一段,重定向(`>` `>>` `<`)、`$()`/反引号/`${}`、ANSI-C 引用(`$'...'`)、后台 `&` 一律拒绝。这样 `git show HEAD:x | head -80`、`git status && git diff`、`cd <仓库根> && git show HEAD` 都能跑,而 `git status; curl evil.com`、`git status && cat /etc/passwd` 仍然被拦。

为什么不是更严格的"禁止一切复合命令":那会把模型最自然的写法(`cd <repo> && git show HEAD`)整条拒掉,每次白烧一个 turn,却并没有降低风险——同样两条命令分开调用本来就被允许。真正的安全边界是"每一段都在只读面内",而不是"不许分段"。

**hook 只能做减法(二进制实证)**:claude 2.1.311 的输出清洗器只透传 `permissionDecision: "deny" | "ask"`,`"allow"` 被显式丢弃(`permissionDecision==="deny"||==="ask"?…:void 0`),`updatedInput` 在这条路径上也没有落地。所以 hook 永远无法绕过权限引擎——这正是 allowlist 里必须有 `Bash(cd:*)` 的原因:让"形状"过引擎的闸,再由 hook 判定 `cd` 到底落在哪。任何 hook 放行、引擎拒绝的组合只会浪费一个 turn,不会削弱安全。

**读取同样被限制在仓库内(v1.15.0)**:`find`/`rg` 与管道 sink(cat/head/tail/wc/uniq/cut/tr/grep)的路径参数都做仓库范围校验——`~`、`$VAR`、`..`、花括号扩展、绝对路径、以及仓库内软链接/junction 指到外面,全部拒绝(用 realpath 判定,与 Edit/Write 的边界同一套规则)。Git 的 pathspec 不做路径校验(git 自己限制在树内),但逃逸口全部一次性拒绝:`git diff --no-index`、`git blame --contents`、`--output=` 写形态、`sed -f`、`wc/sort --files0-from`、`uniq` 的第二个位置参数(写文件);`sed` 不在许可的 sink 之列(其脚本语言的"执行/写/读"形态无法被可靠解析,实测 `s/.*/命令/e` 执行过)。hook 拿不到仓库根时对 Bash 直接 fail-closed。

它同时补上了**前缀规则本身堵不住的写/执行形态**:`find` 与 `rg` 是通过前缀规则放行的只读工具,但 `find -delete / -exec / -execdir / -ok / -okdir / -fprint / -fprintf / -fls` 会写文件或执行程序,`rg --pre / --pre-glob` 会对每个文件跑任意程序,`sort -o / --compress-program / -T` 会写文件或执行程序,`git branch <name>` 会建分支(flag 守卫拦截写形态)。这些在 hook 里按段直接拒绝(allowlist 里也加了对应 deny 规则,但确定性控制是 hook),普通读取不受影响。

> ⚠️ **`--settings` 替换的是整个 `hooks` 键**:注入的临时文件里只有本 MCP 的 hook,用户 `~/.claude/settings.json` 里自己配置的 hooks 在**本次子进程内不会生效**(其它键如 `env`、`model`、`permissions` 不受影响)。这是 CLI 的行为,不是遗漏——如果你依赖自己的 hook 在 review 期间运行,目前需要另想办法。

**策略只有一份**:`src/shell-guard.js` 导出 `BASH_POLICY`,`prompt.js` 用它渲染 `Bash(...)` 权限规则,hook 用同一份表逐段判定——所以"hook 放行"与"引擎放行"不可能漂移。契约由 `scripts/shell-guard-test.mjs`(327 条)、`scripts/p0-regression-test.mjs`(256 条:2026-10 各轮审计的全部 P0 形态——ANSI-C 引用/反斜杠转义/紧贴短选项、`.GIT` 大小写变体、fix 模式持久化路径、仓库自带 git driver 的 RCE 形态、符号链接冻结、`config.worktree`、`node_modules` attributes、hook 的 fail-closed payload 形状)与 `scripts/audit-fixes-test.mjs`(318 条)锁定——**改策略前先跑 `npm test`**。关掉它:`CLAUDE_REVIEW_SHELL_GUARD=0`(此时整体会 fail-closed 拒跑,见下文"纵深失效即拒跑")。

重定向(含 `2>/dev/null`)仍然整体拒绝:它是绕过 allowlist 的读写原语,改成"允许读重定向"需要先证明每个目标路径都无害,代价大于收益。需要看输出就直接让命令写到 stdout。

**v1.18.5 补齐的边界(第三轮审计的 7 P0 / 14 P1)**

- **git 选项缩写不再能绕过 deny 表**:`git`/`sort`/`wc`/`rg` 接受无歧义缩写,deny 表现在按"无歧义前缀"匹配,`--cont`(`--contents`)、`--files0`(`--files0-from`)、`--ignore-revs-file`/`--mailmap-file` 等一律拒绝;`find -- <path>` 的 `--` 也按路径校验。
- **`gh pr comment --body-file/-F`** 的文件参数走仓内校验(`postToPR` 也不例外),不再能把宿主机凭据文件 POST 出去。
- **`echo` 的引号按字符判定**:只有未加引号的 glob/`~` 会被拒(`echo "prefix"*`、`echo ~"/".ssh/*` DENY),合法字面量(`echo '*'`、`echo 'price: $5'`)仍可用。
- **仓库配置改为从 git 枚举**:主工作树的 `.git/config.worktree`、`git ls-files` 枚举出的全部 `.gitattributes`(含 `node_modules/` 内)、越界 `[include]` 目标、超过 200 个 include 的仓库,全部拒绝;`protocol.ext.allow`/`protocol.file.allow`/`url.*.insteadOf`/`remote.*.vcs` 被扫描器拒绝,且子进程 git 环境直接中和 `protocol.ext.allow=never`/`protocol.file.allow=never`(`ext::` 传输在评审进程里不可用)。
- **读边界覆盖 Grep/Glob 与 Bash**:hook 的 matcher 改为 catch-all,`Grep`/`Glob` 的 `path` 与 `Read` 同样受仓内约束与密钥黑名单约束;Bash 命令里引用 `.env`/`id_rsa`/`.ssh/`/`credentials`/`.npmrc`/`.git-credentials` 等密钥路径同样被拒(此前只有 `Read` 工具受限)。
- **fix 模式的持久化拒绝扩表**:`.husky/**`、`.github/actions/**`、`.gitlab-ci.yml`、`Jenkinsfile`、`Makefile`、`.pre-commit-config.yaml`、`.devcontainer/**`、`.cargo/config(.toml)`、`.circleci/**`、`.npmrc` 与密钥文件均不可写;`package.json` 只在写入内容含 `scripts`/`postinstall`/`preinstall` 时拒绝(普通依赖改动仍允许)。
- **策略指纹现在覆盖 hook 的两张表**(`PROTECTED_WRITE`/`SECRET_READ` 移入 `shell-guard.js` 并纳入哈希),升级只改了 hook 策略时 stale-policy 自检同样会拒绝。
**v1.18.6 收口(结构性与残余)**

- **git 加固只有一张表**:子进程 env、服务器自己的 `-c`、扫描器的键表由 `GIT_HARDENING` 单一来源生成,启动时断言"服务器/扫描器保护的键必须在子进程 env 里"——杜绝 1.18.5 P0-1 那类"加错表"回归。
- **containment 只有一份实现**:`realpathNearest`/`repoRelative`/`isInsideRepo(..., {allowRoot})` 在 `src/shell-guard.js`,hook、guard 与仓库元数据读取共用。
- **递归扫描的读边界**:`rg --hidden/--no-ignore/-u/--unrestricted` 与根级递归 `grep -r/-R` 被拒(它们会读到 `.env` 等被忽略的密钥);scoped 搜索(`rg -n x src`)保持可用。
- **fail-closed**:仓库的 `.git/config` 不可解析、或 `git ls-files` 无法枚举 attributes(如坏 index)时直接拒绝评审,不再只记 note。
- **`package.json` 的 Edit/MultiEdit** 同样检查 install 钩子(此前只查 Write)。
- **策略指纹**现在覆盖内联规则函数的源码:只改规则、不改表的升级也会触发 stale-policy 拒绝。
- **lockfile** 的 94 个 tarball URL 从 `registry.npmmirror.com` 改为 `registry.npmjs.org`(路径与 integrity 不变)。
**v1.18.7 边界(复核 + 最终审计)**

- **`find` 的长选项两种拼写都拒**:GNU find 历史单横线长选项(`-files0-from`)与双横线形式(`--files0-from`、`--files0`)全部拒绝,含无歧义缩写。
- **`find -D` / `-O`**:只吞 debugopts(字母/逗号)或 level(数字)值,其它后续 token 都是搜索路径并参与校验(`find -D /etc`、`find -O /etc` 此前可枚举宿主目录)。
- **自审保护整条 guard/spawn 管线**:`src/*.js`、`scripts/pre-tool-hook.js`、`scripts/init.mjs`、`scripts/lib/**`、`skills/**` 在 fix 模式下不可写——改 `src/index.js` 或 `src/claude.js` 与改 hook 一样能让后续所有 run 卸防。
- **hook 对无法判定的 payload fail-closed**:非法 JSON、空 stdin、非对象、缺 `tool_name` 一律拒绝(此前 exit 0);解析成功但属于 hook 不负责的工具仍放行(那些由服务端 allowlist 管)。
- **Node ≥ 20**:声明的最低运行时、CI 矩阵(20/22/24)与 README 徽章一致,并由 pack-test 断言锁住。
- **`postToPR` 需要完整 PR 选择器**:`pr: "123"` 会被 `gh` 按被审仓库自己的 remote 解析,而被审仓库是不可信内容——评论目标因此由仓库控制。`owner/repo#N` 或 PR URL 把目标钉死在调用方指名的仓库;只读评审用裸号仍然允许(`scope=pr` 的既有行为)。
- **自审保护按身份而非路径形状**(v1.18.8):`src/*.js`、`scripts/init.mjs`、`scripts/lib/**`、`skills/**` 只在**被审仓库就是本包**时禁写(hook 与服务端始终从包安装目录加载,普通仓库里改这些文件对运行中的 guard 没有影响)。普通仓库的 fix mode 可以正常编辑 `src/*.js`;自审时这些文件仍需手工改。

hook 还有第二条确定性规则:**git 的 `--output=` / `--output-directory=` 会让"只读"命令写文件**(`git diff/show/log --output=<file>`、`git format-patch --output-directory=` 实测都会落盘;`--output-to=` 不是真实旗标)。deny glob 对这条无能为力——Bash 规则是整条命令前缀匹配,`Bash(git diff --output=*)` 拦不住 `git diff HEAD --output=x`,而中间带 `*` 的规则语义无法从 CLI 外验证——所以这一条由 hook 直接判掉。注意 `grep --output`、`find -print` 等非 git 的 `--output` 不受影响。

**写入面收敛(hook 的另一半职责,v1.5.0)**

Edit / Write / NotebookEdit / Read 也被 hook 审查,且以 `CLAUDE_REVIEW_REPO_ROOT` 为界:目标路径必须解析到被审仓库内,`.git/**`、`.env*`、`id_rsa*`/`*.pem`/`*.key` 等密钥路径一律 deny;没有传 `repoRoot`(hook 配置没生效)时 fail-closed 全部拒绝,而不是放行。边界判定走 **realpath**(两侧都解析真实路径),所以仓库里的目录 junction/软链接(`repo/link -> ~/.ssh` 这种"词法在仓库内、实际在外面"的逃逸)也会被拦下(有回归测试锁定);未命中已有目标的写入按"最近的已存在祖先"解析。

**仓库即代码执行面(v1.5.0 新增,v1.7.0 起按"存活"判定)**

git 会执行**仓库自己**的配置:`core.fsmonitor`、`diff.<driver>.command`、`.gitattributes` 的 filter/textconv 驱动、`core.hooksPath`、`[include] path = ../x`(可指向仓库外)。`-c` 覆盖只能中和已知键,驱动名是任意的。所以每次运行前会扫描 `.git/config`(含 include 链)与各级 `.gitattributes`:**发现仓库控制的程序执行就直接拒跑**,而不是带病开审。

扫描是精确的,不会误伤普通仓库:`.gitattributes` 里写了 `*.png filter=lfs` 但驱动定义在用户全局 config(git-lfs 的常见装法)时**不**拒;`core.fsmonitor = true`(布尔,且本来就被 `-c` 中和)也**不**拒。v1.7.0 起进一步区分"活的"与"死的"配置:

- **死路径降级为提示**:从另一台机器整目录拷贝/迁移过来的仓库,`.git/config` 里常带着原机器的绝对路径(如在 Windows 上看到 `core.hooksPath = /Users/kent/Desktop/...`)。路径在本机**不存在就执行不了任何东西**——降级为报告中的一条 note,不再阻塞 review(有回归测试锁定)。
- **`core.hooksPath` 即使是活的也只提示不拒**:git hooks 只在 commit/merge/rebase 这类**变更型**操作时触发,而评审 allowlist 从不授权任何变更型 git 命令;本 MCP 自己的 git 调用也总是带着 `-c core.hooksPath=` 中和它。fsmonitor/textconv/external/filter 驱动则不同——它们在 `git status`/`git diff` 这些**只读命令**上就会触发,所以活的一律拒,死的提示后放行。

**纵深失效即拒跑(fail closed)**

hook 没装上、`guardSelfTest()` 自检失败(hook 真的能拦下一条链式命令、真的放行一条干净命令)或 guard 状态为 `failed` 时,两个工具都会**直接拒绝发起 review**,并说明缺了哪一层——因为 manual 模式的 allowlist 单靠自己挡不住前缀匹配绕权。`CLAUDE_REVIEW_ALLOW_UNGUARDED=1` **只**解锁"你显式关掉的 guard"(`CLAUDE_REVIEW_SHELL_GUARD=0`);**安装失败/自检失败永远不可绕过**(那种情况下的运行会宣称有 hook 而实际没有任何命令被拦)。确实要完全无防护运行,显式设 `CLAUDE_REVIEW_SHELL_GUARD=0`。

**版本地板(v1.5.0 新增)**

`--permission-mode manual` 需要 CLI ≥ 2.1.200、`--permission-prompts none` 需要 ≥ 2.1.259。把未知旗标传给旧版 claude 会以一句难懂的报错结束整次运行,所以运行前读一次 `claude --version` 做门控(版本比较对 `2.1.301-beta.1` 这类预发布版安全),不达标就不传该旗标并在结果里说明退化情况。`claude_review_env` 会打印这两个地板与当前 CLI 版本。

**输入校验(v1.3.0 新增,全部在发起付费调用前)**

| 输入 | 规则 | 原因 |
|---|---|---|
| `repoPath` | 必须是绝对路径(`~/` 会展开) | 相对路径会按 MCP 服务器自己的 cwd 解析,可能审错仓库还花了钱 |
| `repoPath` | 必须是 git/hg/svn 仓库 | 所有 scope 都读 git 历史,非仓库只会得到一次注定失败的付费调用 |
| 空仓库 | 无提交且无未提交改动 → 直接报错 | 没什么可审 |
| `pr` | 只接受 `123` / `#123` / `owner/repo#123` / GitHub PR URL | 该值会拼进评审提示词 |
| `outputLanguage` | 单行、≤20 字符、无反引号、无句末标点 | 防止夹带指令;语言名足够短(中文/English/English (US)) |
| `baseRef` | 纯 ref 字符,且不接受以 `-` 开头 | 会拼进 `git merge-base <ref>`;以 `-` 开头会被 git 当成旗标(`git merge-base --output=<file>` 还会写文件),因此直接拒绝而不是"忽略" |
| `focus` | **单行**、≤2000 字符、拒绝控制字符(含 \t\n\r) | 换行/制表符会被拼进提示词结构,因此拒绝 |

**取消语义**:MCP SDK 对已取消的请求会**故意丢弃响应**(协议规范如此,客户端本就已知自己取消了)。所以取消后你看不到"已取消"的返回值,但子进程树会在取消后约 2 秒内被 taskkill 杀掉、立刻停止计费;客户端断开 stdio 时同样会杀掉在跑的 review。

**可观测性(v1.3.0 新增)**:结果元数据里带 `permission_denials` 与 `reviewer_subagents_spawned`。manual 模式下 allowlist 漏项不会中断运行、只会让评审静默退化(例如 reviewer 子代理根本没起来),这两个字段把这种退化变成可见的。fix 模式还会比对运行前后的 `git status`,若工作区毫无变化会明确报 `FIX MODE VERIFICATION FAILED`——评审者"描述"了修复但没落盘时不会被误判成功。

v1.4.0 起元数据另加 `bashCalls`(本次发起的 Bash 调用数)与 `shellGuardBlocked`(被 hook 拦下的命令数),并区分两类拒绝:被守卫拦下是 **hook 正常工作**(提示模型改用策略内的命令即可,不算评审退化),只有其它拒绝才报"评审可能不完整"。若出现 `⚠ BASH COMMAND(S) RAN WITHOUT BEING BLOCKED`,说明有命令绕过了 hook 真实执行了,此时才需要把该仓库当敌对环境处理。

v1.10.0 起返回**每个评审子代理各自的报告**(`## Reviewer subagent reports` 小节 + structuredContent 的 `subagents[]`)——此前只有父代理的综合报告,子代理内容只存在于日志文件里,这正是"同样提示词在 Claude Code 里能看到多个专家干活、通过 MCP 只有一份综述"的原因。同时新增**模型溯源**:claude 2.1+ 会把子代理事件(含它实际使用的模型)转发到父流,MCP 据此比对"请求的模型 vs 实际运行的模型",一旦发现被改写(CC Switch 等网关、或 `~/.claude/settings.json` env 里的 `CLAUDE_CODE_SUBAGENT_MODEL` / `ANTHROPIC_DEFAULT_*_MODEL_NAME`——claude 会在 stderr 打 `[claude-code:unrecognized_model]`),就在结果里明确报 `⚠ MODEL SUBSTITUTION`:这份输出与直接在 Claude Code 里跑同一提示词**不可比**。运行前也会针对 settings.json env 里的同名改写与"进程环境的 `ANTHROPIC_BASE_URL` 被 settings 覆盖"提前预警。

- 运行日志(含被审代码引用)写入 `%TEMP%/cc-code-review/`,目录 0700、文件 0600,只保留最近 20 份;增量子代理报告 `subagents-*.jsonl` 用同一目录、同样 0600,并纳入同一套保留与清扫。
- CLI 自带一份内置安全命令白名单(`ls`、`cat`、`echo` 等只读工具),即使不在 `--allowedTools` 里也会放行。**正因如此,`echo` 的收紧必须落在 hook 上**:hook 只允许字面文本——`$VAR` 展开与**未加引号**的通配符/`~`/仓外路径一律拒绝,加引号的字面量(`echo 'price: $5'`、`echo '*'`)仍然可用(否则被注入的 reviewer 可用 `echo $ANTHROPIC_API_KEY` 把宿主凭据写进报告)。已实测 `node -e`、`python -c`、`rm`、`git push`、`git commit`、`curl`、Write 工具调用**全部被拒**,所以这不是提权口子,但"只放行白名单里的命令"要按"白名单 + 这批内置只读命令"来理解。
- 模型可以把被 hook 拦下的复合命令拆成多次允许的调用分别执行——hook 拦的是"一条命令里串多步",不是"一个回合里做多件事"。真正的能力边界仍然是 manual 模式的 allowlist,hook 保证每一步都可被单独审计。
- 已知残留风险:仓库内其他文件的 fix 改动、prompt 注入诱导的"看起来合理"的编辑——fix 模式改动留在工作区不 commit,请像审 PR 一样 `git diff` 检查后再采纳。
- 已知残留风险(Windows):日志目录/文件的 0700/0600 是尽力而为——NTFS 不存 POSIX 权限位,同一台机器上的其他本地用户理论上可读 `%TEMP%\cc-code-review\run-*.log`(内含被审代码引用)。需要更严的隔离请设 `CLAUDE_REVIEW_LOG_DIR` 指向受限目录,或接受该风险(多数单用户桌面场景等同无风险)。

## 环境变量

| 变量 | 默认 | 说明 |
|---|---|---|
| `CLAUDE_CODE_PATH` | 自动探测 | claude 可执行文件 / cli.js(不接受 shell shim) |
| `CLAUDE_REVIEW_TIMEOUT_MIN` | `30` | 默认超时(分钟);仅改默认值,显式 `timeoutMinutes` 优先 |
| `CLAUDE_REVIEW_MAX_BUDGET_USD` | `0` | 默认花费上限,`0` = 不设上限;仅改默认值,显式 `maxBudgetUsd` 优先 |
| `CLAUDE_REVIEW_MODEL` | - | 默认模型 |
| `CLAUDE_REVIEW_AUTO_INSTALL_SKILL` | 关闭 | 设 `1` 时 `installSkillIfMissing` 默认变 true(仅 pipeline=skill 有意义;即便如此也只在 skill 缺失时安装) |
| `CLAUDE_CONFIG_DIR` | ~/.claude | skill 的安装/查找根(进程环境与 `~/.claude/settings.json` 的 env 块都会读,与 claude 子进程一致) |
| `CLAUDE_REVIEW_SKILL_DIR` | - | 测试覆盖:直接指定 skill 目录,优先于上者 |
| `CLAUDE_REVIEW_SHELL_GUARD` | `1` | `0` 关闭 PreToolUse 的 Bash 分段守卫(仅用于排查误报,不推荐;单独设它不会放行,还会连带拒跑) |
| `CLAUDE_REVIEW_ALLOW_UNGUARDED` | - | 设 `1` 才允许"guard 被显式关闭"时仍然开跑。**对安装失败/自检失败无效**(那两种情况永远拒跑)。默认拒跑 |
| `CLAUDE_REVIEW_LOG_DIR` | `%TEMP%/cc-code-review` | 运行日志目录(日志含被审代码引用;Windows 上无 POSIX 权限位,可指向自有 ACL 的目录) |

提示:若宿主环境残留 `ANTHROPIC_API_KEY` 等认证变量但 settings.json 未管理它们,可能与 CC Switch 冲突(`claude_review_env` 会告警);网关用户可在 `~/.claude/settings.json` 加 `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"` 减少非必要流量。

## 成本与时长

- 每次真实 review 消耗 Claude 额度(走你的订阅/网关)。默认 effort=max:10 个 finder + 每个存活候选 1 个 verifier + 1 个 sweep(实测 17 个子代理、$5.7、约 23 分钟;并行度受网关并发上限影响,实测 17 个并发时 5 个失败);显式 `effort: "low"` 走内置单遍,约 $0.1–3。默认**不设费用上限**(`maxBudgetUsd: 0`),由 `timeoutMinutes` 兜底。
- **增量捕获**:每个评审子代理一完成就立刻追加进 `%TEMP%/cc-code-review/subagents-*.jsonl`(一行一个),并可通过 `claude_review_status` 在运行中轮询拿到——原生 skill 要等全部子代理跑完才出报告,而 429/超时/取消随时可能打断;被打断时已完成的报告随错误一起返回。
- **429 抢救**:被限流/额度耗尽打死的子代理,其工作会从启动确认里的 `output_file` 转录尾部恢复(标记 `partial (output_file)`),已花的 token 不白扔。
- `claude_review_env` 与版本/flag 探测免费(结果有缓存)。

## 用法示例(任意 MCP 客户端)

重启宿主(ZCode / Claude Code / Cursor 等)后直接说,例如:

> 用 cc-code-review MCP 对 C:\path\to\repo 的未提交改动跑一轮 code review(effort 用 max),然后根据 findings 修复,修完再复核一遍。

## 测试 / 维护

```bash
npm install
npm test                                         # 跑全部免费套件并统一汇总(shell-guard 327 + 审计修复 318 + P0 回归 256 + validation 31 + plugin 45 + pack 52 + smoke + cross-cases 156/156),任一失败即非零退出
npm run init                                     # 环境自检 + 按本机写入 ZCode 注册(免费)
node scripts/validation-test.mjs                # 全部输入校验路径(免费,不发 API 请求)
node scripts/plugin-test.mjs                    # Agent Plugins 一致性:plugin.json / mcp.json / skills/(免费、离线)
node scripts/cross-cases.mjs                    # 27 个 MECE 用例(默认免费跑 26 个、156 项检查;含 ultracode/workflow + 舰队缩放、取消后保留已完成报告、误判 ack 被 notification 纠正等端到端;含桩 claude 的免费异步生命周期 + 子代理报告/模型溯源端到端;C9 付费需 CC_LOOP_PAID=1)
node scripts/paid-smoke.mjs [--cap=2]           # 真实 CLI 端到端 smoke:带三个已知 bug 的临时仓库,effort/reviewLevel=low,默认不设费用上限(计费,实测约 $0.5)
node scripts/e2e-test.mjs <repoPath> [json]     # 真实 review(计费)
node scripts/followup-e2e.mjs <repoPath> <sessionId> # 真实 followup(计费)
node scripts/cancel-test.mjs <repoPath>         # 取消后进程树是否被立刻杀掉(计费,几美分)
```

`validation-test.mjs` 覆盖:相对 `repoPath` 拒绝、恶意 `pr`(含前导 `-`)拒绝、`outputLanguage` 超 20 字/句末标点/多行拒绝、`focus` 含换行或制表符拒绝、`followup` 消息超 100000 字拒绝、`claude_review_status` 无 jobId 的 cancel 拒绝、空仓库拒绝、非 git 目录拒绝、`maxBudgetUsd=0` 被 schema 接受、`baseRef` 以 `-` 开头拒绝、`focus` 含控制字符/超长拒绝、guard 未生效时两个工具都 fail-closed 拒跑、以及 `CLAUDE_REVIEW_ALLOW_UNGUARDED=1` 的显式解锁。全部在付费调用之前返回——唯一例外是"clean repo 通过 preflight"这条(它会真的发起一次有预算上限的付费 run),默认跳过,设 `CC_REVIEW_ALLOW_PAID_PROBE=1` 才执行。

`shell-guard-test.mjs` 覆盖:链式/重定向/替换命令必须拦、`find -delete/-exec/-fprintf` 与 `rg --pre` 必须拦而普通 `find`/`rg` 读取必须放行、`git log --format="%h; %s"` 这类引号内分隔符必须放行、引号不配对 fail-closed、hook 进程的 stdin→deny 契约、Edit/Write/Read 的仓库边界与 `.git/`/密钥路径拦截、仓库内 junction/软链接逃逸(realpath 边界),以及一次真实 review 的 17 条命令回放(确认被拦的命令不会被误报成"逃逸")。

`plugin-test.mjs` 覆盖可移植的 Agent Plugins 表面:`plugin.json`(closed schema、canonical `$schema`、name/version/license 约束)、`mcp.json`(closed 顶层、与 plugin.json 同版本、stdio variant 规则、保留 env 名、`cwd` 形式)以及 `skills/*/SKILL.md`(Agent Skills frontmatter、name 与目录名一致、description 长度、正文行数、报告合同存在)。离线运行,不拉取远程 schema。

`audit-fixes-test.mjs` 覆盖三轮独立审计的每条修复:deny 列表覆盖面与"同一规则同时出现在 allow/deny 里变成死规则"的冲突、**allow 规则被 deny 规则前缀遮蔽**(`Bash(git remote -v)` vs `Bash(git remote:*)`,真实 review 发现的)、`postToPR` 授权不是死代码、版本比较(含预发布号)、git hardening 的 `-c` 参数、`repoRunsGitPrograms` 的精确性(fsmonitor 布尔不误伤、LFS 全局驱动不误伤、`[include]` 链与 `.gitattributes`↔config 交叉核对都查得出)、hook 的 `git --output=` 规则、提示词里 caller 数据只能是数据,以及一次真实 review 的命令回放(allowlist 与 deny 两侧都要放行)。

**改 `src/shell-guard.js` 的正则前必须跑 `npm test`**:那行正则的注释和模块 docstring 说明了它为什么必须包含 `|` 与 `&`(前缀匹配绕权),单看代码很容易误以为在降低误报率。

**已实测通过的回归项**(Windows / npm 全局安装 / CC Switch 本地网关,无账号登录):manual 模式下完整 review(中文报告、按当时的 ≥80 规则把自标 BUG 评成 CRITICAL/MAJOR(现行为 ≥75)、低分项正确降级到 Notes)、`scope=branch` + `baseRef`(`git log main..HEAD` / `git diff main...HEAD`,`permission_denials=0`、guard 拦下 0 条)、fix 模式落盘且**未 commit**、`.git/` 未动、followup 用返回的 `session_id` 续上同一会话。更早一轮还验证过:带恶意 `.claude/settings.json` 的仓库不再执行仓库控制的 hook、取消后 2 秒内进程树清零。

- 运行日志:系统临时目录下的 `cc-code-review/run-*.log`(Windows 即 `%TEMP%\cc-code-review`;保留 20 份;guard 临时文件与 git 沙箱 HOME 同目录,进程退出时清理,陈旧残留由下一次运行自动清扫)。
- 从 v1.5.0 及更早版本升级(旧名 claude-review-mcp):旧日志目录 `%TEMP%\claude-review-mcp` 不会被新版本触碰或删除,如含敏感引用请手动删除。
- 卸载:删除 ZCode 配置里的 `cc-code-review` 条目;移除 skill 删 `~/.claude/skills/code-review`。

## 授权与致谢

- 本项目以 [MIT](LICENSE) 协议开源。
- 内置的 code-review skill(`skills/code-review/SKILL.md`)改编自 Anthropic 官方的 code-review 插件管线(由 5 个 brief 驱动的 8/10 个并行 finder + 置信度过滤),在此基础上适配了 headless 调用、只读边界,并改写为与 harness 无关的 Agent Skill。
- 感谢 Claude Code 团队与 CC Switch / claude-code-router 等网关项目,让"无账号登录也能跑 review"成为可能。
- 安全模型经过多轮独立交叉审计(Windows / macOS / 认证网关 / 安装形态 / 回归五个视角),详见上文与 [SECURITY.md](SECURITY.md)。发现漏洞请走私有通告,不要开公开 Issue。

TDQS

A4.3/5.0

Scored across 4 tools

Disambiguation5/5

Each tool covers a distinct phase of a single workflow: environment/diagnostic setup, launching a review, polling/cancelling a job, and continuing a prior session. The only potential overlap (claude_code_review vs claude_review_followup) is clearly separated by 'start new run' vs 'pass session_id to continue'. Descriptions explicitly disambiguate async vs sync behavior.

Naming Consistency4/5

All names share a claude_ prefix and a noun-action feel (review_env, code_review, review_status, review_followup), making the family obvious. Minor deviation: claude_code_review inserts 'code' where others use claude_review_*, so the prefix isn't perfectly uniform.

Tool Count5/5

Four tools is a tight, well-scoped surface for a code-review runner: setup check, run, monitor, follow-up. Each tool maps to a necessary step and none feels redundant or missing for the stated purpose.

Completeness4/5

The lifecycle is essentially covered: diagnose/install skill, run review, poll/cancel, and follow up with the same reviewer context. Minor gaps: no dedicated tool to retrieve or list past reports/JSONL transcripts, and no explicit way to configure review parameters (scope, thresholds) beyond defaults.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues