gcr 是一个面向 GitLab Merge Request 和 GitHub Pull Request 的代码审查 CLI。它读取 Git diff,运行确定性规则和可选的 LLM review,输出 JSON/Markdown 报告,并将 finding 发布为平台评论。
当前项目不是 GitLab Marketplace 插件,也不需要安装到 GitLab 服务端。实际接入方式是:在 CI Runner 中执行 gcr review,再执行 gcr post。
Merge Request
|
v
GitLab Runner checkout 完整 Git 历史
|
+--> gcr review --from target --to commit
| |
| +--> Git diff
| +--> 确定性规则
| +--> 可选 LLM + Skill + Constraints
| +--> review-report.json / review-report.md
|
+--> gcr post --platform gitlab
|
+--> inline discussion
+--> summary note
review 本身不访问 GitLab 或 GitHub。平台只影响最后的 post 步骤,因此可以先在本地生成和检查报告,再决定是否发布。
需要 Go 1.21 或更高版本:
go build -o gcr ./cmd/gcr发布仓库的 v* tag 会通过 .github/workflows/release.yml 构建 Linux amd64、Linux arm64 和 macOS arm64 二进制,并发布到 GitHub Release。也可以本地构建:
VERSION=v0.1.0 ./scripts/build-release.sh
cd dist && shasum -a 256 -c checksums.txt业务仓库不需要包含 cmd/gcr 时,在 GitLab CI/CD Variables 中设置 GCR_BINARY_URL,CI 会优先下载该 URL;未设置时继续从 ./cmd/gcr 源码构建:
GCR_BINARY_URL=https://github.com/hh/ci-code-review-agent/releases/download/v0.1.0/gcr-v0.1.0-linux-amd64
下载的二进制应来自受信任的 release 或内部制品库。生产环境建议同时增加 checksum 校验,并固定版本,不要使用 latest。
必须显式指定比较基线:
./gcr review \
--repo . \
--from main \
--to HEAD \
--out review-report.json \
--markdown review-report.md默认不会因为 finding 让进程失败。只有传入 --fail-on-p0 时,存在 P0 finding 才会以退出码 2 结束。
export CI_SERVER_URL=https://gitlab.example.com
export CI_PROJECT_ID=123
export CI_MERGE_REQUEST_IID=45
export GITLAB_API_TOKEN="$GITLAB_REVIEW_TOKEN"
./gcr post --platform gitlab --report review-report.jsonToken 不要写进命令行参数、仓库文件或日志。生产环境应使用 GitLab masked/protected CI variable。
将 examples/gitlab/.gitlab-ci.yml 复制到目标业务仓库根目录并提交。GitLab 只会自动识别仓库根目录的 .gitlab-ci.yml,不会自动执行本项目 examples/ 下的示例文件。
示例配置的核心内容如下:
stages:
- review
code-review:
stage: review
image: golang:1.21
interruptible: true
resource_group: code-review-$CI_MERGE_REQUEST_IID
only:
- merge_requests
variables:
GIT_DEPTH: "0"
script:
- go build -o gcr ./cmd/gcr
- |
./gcr review \
--from "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}" \
--to "${CI_COMMIT_SHA}" \
--out review-report.json \
--markdown review-report.md
- ./gcr post --platform gitlab --report review-report.json
artifacts:
when: always
paths:
- review-report.json
- review-report.md如果业务仓库没有 cmd/gcr 源码,需要在 CI 中先下载预构建的 gcr,或将本项目作为独立 checkout。当前示例假设 gcr 的源码就在业务仓库内。
gcr post --platform gitlab 使用以下变量:
| 变量 | 用途 |
|---|---|
CI_SERVER_URL |
GitLab 地址;GitLab CI 会自动提供 |
CI_PROJECT_ID |
当前项目 ID;GitLab CI 会自动提供 |
CI_MERGE_REQUEST_IID |
当前 MR IID;只在 MR pipeline 中提供 |
GITLAB_API_TOKEN |
推荐使用,Token 需要 api scope |
CI_JOB_TOKEN |
没有 GITLAB_API_TOKEN 时的备用 Token |
推荐在项目的 Settings > CI/CD > Variables 中添加:
Key: GITLAB_API_TOKEN
Value: <project access token or bot user token>
Options: Masked, Protected
使用 CI_JOB_TOKEN 时,要确认 GitLab 版本和项目设置允许 Job Token 调用目标项目的 Merge Request API。遇到 401 或 403 时,优先改用具有 api scope 的 masked token。
GitLab CE 容器只提供 GitLab 服务端,不会自动提供 Runner。没有 Runner 时,Pipeline 会被创建,但长期处于 pending。
在一台能访问 GitLab 的机器上安装 GitLab Runner,然后:
- 进入项目 Settings > CI/CD > Runners,创建 project runner。
- 记录 runner authentication token。
- 使用 Docker executor 注册 Runner:
docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
docker exec -it gitlab-runner gitlab-runner register \
--non-interactive \
--url "http://192.168.1.11" \
--token "<runner-authentication-token>" \
--executor "docker" \
--docker-image "golang:1.21" \
--description "gcr-review-runner" \
--docker-volumes "/var/run/docker.sock:/var/run/docker.sock"- 确认 Runner 的 tags、protected 设置和项目可见范围允许该 MR pipeline 使用它。
- 重新推送一个 commit 或在 Pipeline 页面重试任务。
Runner 的 Docker executor 需要能够拉取 golang:1.21。如果 Runner 所在机器不能访问 Docker Hub,应配置镜像代理或使用内部镜像,并把 CI 文件中的 image 改成可访问的镜像。
- 有
path和新增行号的 finding 会尝试发布为 inline discussion。 - 无法定位到新增行的 finding 会进入 summary note。
- 最后总会发布一条 summary note,包含通过状态、各级别 finding 数量和 inline/summary 数量。
- GitLab API 请求超时时间为 20 秒,CLI 的 post 总超时时间为 30 秒。
一次验证后的 MR 页面效果如下,包含 inline finding 和 review summary:
LLM review 只有传入 --llm 才会启用。未启用时仍会运行确定性规则。
Ollama 使用原生 API 地址,不要在 URL 后面追加 /v1:
export GCR_LLM_PROVIDER=ollama
export GCR_LLM_BASE_URL=http://localhost:11434
export GCR_LLM_MODEL=Qwen2.5-14B-Instruct-Q5_K_M:latest
./gcr review --from main --to HEAD --llm在 GitLab Runner 中使用 Ollama 时,localhost 指向 Runner job 容器,而不是宿主机。应使用 Runner 能访问的地址,例如宿主机网关、局域网地址或内部 Ollama 服务地址。
export GCR_LLM_PROVIDER=openai
export GCR_LLM_BASE_URL=https://api.openai.com
export GCR_LLM_MODEL=gpt-4o-mini
export GCR_LLM_API_KEY="$OPENAI_API_KEY"
./gcr review --from main --to HEAD --llm也支持其他 OpenAI-compatible gateway,只需将 GCR_LLM_BASE_URL 设置为该 gateway 的地址。
命令行参数优先于环境变量,环境变量优先于默认值:
| 配置 | 命令行参数 | 环境变量 | 默认值 |
|---|---|---|---|
| Provider | --llm-provider |
GCR_LLM_PROVIDER |
ollama |
| Base URL | --base-url |
GCR_LLM_BASE_URL |
http://localhost:11434 |
| Model | --model |
GCR_LLM_MODEL |
Qwen2.5-14B-Instruct-Q5_K_M:latest |
| API key | 无 | GCR_LLM_API_KEY,其次 OPENAI_API_KEY |
空 |
| Diff 上限 | --max-diff-bytes |
无 | 60000 |
| Review 超时 | --timeout |
无 | 10m |
模型输出必须是 JSON。若模型不可用、路由结果无法解析或结果无法定位到实际 diff,程序会记录 warning,并跳过对应 LLM finding;确定性规则仍会保留。
Skill 是 Markdown 文件,文件名必须是 SKILL.md。默认目录是当前工作目录下的 skills/,也可以通过 --skills-dir 或 GCR_SKILLS_DIR 指定一个目录。
---
name: database
category: performance
languages: [go, java]
description: 数据库查询、连接池和事务边界。
---
审查新增代码中的 N+1 查询、无界结果集、连接泄漏、事务范围错误和缺少超时。
只报告能从当前 diff 定位的问题。字段说明:
name:Skill 名称,LLM 路由返回的名称必须匹配它。category:用于向模型描述 Skill 类型。languages:语言列表;*表示所有语言。description:Skill 索引中的简短描述。- front matter 后的正文:真正发送给 review 模型的审查指导。
启用 --llm 后,流程是:
- 按优先级加载各层目录中的所有
SKILL.md。 - 根据 diff 中的语言和 Skill 索引,让模型返回相关 Skill 名称。
- 只将模型选中的 Skill 正文传给 review 模型。
- 将约束文本、选中的 Skill 和 diff 一起发送给 review 模型。
没有可用 Skill、Skill 路由失败或模型返回非法 JSON 时,会退回通用 senior-engineer review guidance,不会自动假设某个 Skill 一定相关。
当前实现会自动合并多层 Skill,同名 Skill 由后面的高优先级层覆盖前面的定义。优先级从低到高为:仓库内置 skills/、全局 ~/.gcr/skills/、企业共享目录、个人 ~/.gcr/personal-skills/、企业项目目录、项目 .gcr/skills/。
全局共享 Skill:
export GCR_SKILLS_DIR=/opt/gcr/skills
./gcr review --llm --from main --to HEAD企业项目 Skill 放在:
/opt/gcr/skills/projects/<CI_PROJECT_PATH>/SKILL.md
例如 CI_PROJECT_PATH=group/service-a 时,目录为 /opt/gcr/skills/projects/group/service-a/。GCR_SKILLS_DIR 也可以通过 review 参数 --skills-dir 指定,命令行参数优先于环境变量。
项目专用 Skill:在项目中维护 .gcr/skills/,并在 CI 中显式指定:
./gcr review \
--llm \
--skills-dir .gcr/skills \
--from "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}" \
--to "${CI_COMMIT_SHA}"如果项目 Skill 还需要内置 Skill,应将需要的内置文件复制到项目目录,或者在 CI 阶段组装一个合并后的临时目录。不要依赖本地开发机的 ~/ 内容影响 CI。
个人本地 Skill:
mkdir -p ~/.gcr/personal-skills/my-team
cp my-personal-skill/SKILL.md ~/.gcr/personal-skills/my-team/SKILL.md个人 Skill 会参与本地自动合并,但不会进入没有同样 HOME 目录的团队 CI。建议把团队规则放进仓库或受控的共享目录,把个人偏好只用于本地 review。
Constraints 与 Skill 不同:Constraints 是 review 的总体规则和组织要求,当前实现支持多层加载,并且后加载层覆盖先加载层。
优先级从低到高如下:
- 内置规则:程序内置 senior-engineer guidance
- 全局:
~/.gcr/constraints.md - 企业全局:
<constraints-dir>/global.md - 个人:
~/.gcr/personal.md - 企业项目:
<constraints-dir>/projects/<project>/constraints.md - 项目:
<repo>/.gcr/constraints.md - 一次性自定义:
--constraints-file <path>
--constraints-file 是最高优先级且必须存在;文件不存在会直接报错。constraints-dir 可通过 --constraints-dir 或 GCR_CONSTRAINTS_DIR 设置。项目名优先取 CI_PROJECT_PATH,其次 GITHUB_REPOSITORY,最后取仓库目录名。
/opt/gcr/constraints/
├── global.md
└── projects/
└── group/
└── service-a/
└── constraints.md
~/.gcr/
├── constraints.md
└── personal.md
project/
└── .gcr/
└── constraints.md
~/.gcr/constraints.md 或企业全局约束:
- 所有网络调用必须有超时,并且错误必须可观测。
- 不允许在日志中输出 token、密码或完整用户隐私数据。项目约束 .gcr/constraints.md:
- Public API 行为必须保持向后兼容。
- 数据库 schema 变更必须提供回滚或兼容迁移方案。
- 新增并发代码必须说明生命周期和取消路径。CI 中使用企业约束:
./gcr review \
--llm \
--constraints-dir /opt/gcr/constraints \
--from "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}" \
--to "${CI_COMMIT_SHA}"重要:约束是追加文本,不是结构化规则引擎。发生冲突时,模型会收到“后面的高优先级约束覆盖前面的约束”的说明,但最终仍依赖模型遵循文本。高风险安全规则应同时使用确定性规则或 CI gate,不要只依赖 LLM。
当前内置规则只检查新增行:
| Rule | 级别 | 说明 |
|---|---|---|
secret-detection |
P0 | 疑似 token、password、API key 或 secret |
private-key |
P0 | 私钥头部 |
dynamic-execution |
P2 | 新增 eval( 或 exec( |
todo-fixme |
P4 | 新增 TODO/FIXME |
long-line |
P4 | 新增行超过 180 字符 |
large-change |
P3 | 单文件新增超过 400 行 |
可以使用 JSON 文件启停内置规则、调整严重级别和修改长行阈值:
{
"rules": [
{"id": "todo-fixme", "enabled": false},
{"id": "long-line", "severity": "P3", "max_length": 160},
{"id": "dynamic-execution", "severity": "P1"}
]
}运行时指定:
./gcr review \
--rules-file .gcr/rules.json \
--from main \
--to HEAD未知 rule ID、非法 severity 或负数 max_length 会直接报错。规则文件只覆盖当前内置规则,不会动态执行 JSON 中的代码。
外部命令通过参数接入:
./gcr review \
--from main \
--to HEAD \
--lint-cmd "go vet ./..." \
--test-cmd "go test ./..." \
--security-cmd "semgrep --config=auto ."Semgrep 和 npm audit 可以输出结构化 finding。命令必须显式输出 JSON:
./gcr review \
--from main \
--to HEAD \
--security-cmd "semgrep --config=auto --json ."
./gcr review \
--from main \
--to HEAD \
--security-cmd "npm audit --json"Semgrep finding 会使用结果中的 rule ID、path、start/end line、message、snippet 和 severity;npm audit finding 会定位到 package.json,并保留依赖名、漏洞等级和 advisory 标题/URL。npm audit --json 因发现漏洞而返回非零退出码时,不会额外生成通用 P0;扫描器无法解析或命令本身失败时才会生成检查失败 finding。
普通外部命令失败会被写成 P0 finding,但只有传入 --fail-on-p0 才会让 review 进程以退出码 2 失败。这样可以先以 artifact/comment 形式观察,再逐步启用阻断。
GitHub 适配器使用 Pull Request Reviews API 发布 summary 和行级 review comment。需要 token 具备 pull_requests: write 权限:
export GITHUB_TOKEN="$GITHUB_TOKEN"
export GITHUB_REPOSITORY=owner/repo
export GITHUB_PR_NUMBER=123
export GITHUB_SHA=<pull-request-head-sha>
./gcr post --platform github --report review-report.jsonGitHub 所需变量:GITHUB_TOKEN、GITHUB_REPOSITORY、GITHUB_PR_NUMBER 或 PR_NUMBER。GITHUB_SHA 用于行级 comment 的 commit_id;缺失时程序会读取 PR head SHA。没有 path/line 的 finding 会保留在 summary 中。如果 Reviews API 失败,程序会回退为普通 PR summary comment。
- 不要把
GCR_LLM_API_KEY、OPENAI_API_KEY、GITLAB_API_TOKEN或GITHUB_TOKEN写入仓库。 - Token 使用 masked/protected CI variables,并限制 project、scope 和有效期。
- Runner job 使用完整 Git 历史,必须保留
GIT_DEPTH: "0",否则origin/<target-branch>可能不存在。 - LLM review 只处理 diff,且默认最多发送 60000 bytes;大改动会被截断。
- 将
review-report.json和review-report.md作为 always artifact,方便排查 comment 发布失败。 - P0/P1 gate、密钥扫描和依赖扫描等强约束应使用可复现的确定性工具;LLM 适合作为补充审查。
项目没有可用 Runner,或 Runner tag/protected 设置不匹配。先在 Settings > CI/CD > Runners 确认 Runner 在线,并确保它允许执行该 MR pipeline。
确认运行在 MR pipeline 中,并检查 CI_PROJECT_ID、CI_MERGE_REQUEST_IID 和 GITLAB_API_TOKEN。本地运行时需要手动设置这些变量。
GitLab 需要 MR diff version 的 base/start/head SHA。确认 MR 有有效 diff,并且 Runner checkout 不是 shallow clone。无法定位的 finding 会退回 summary note。
检查 --llm 是否传入、Runner 能否访问 GCR_LLM_BASE_URL、模型是否支持返回 JSON,以及 --skills-dir 下的 SKILL.md front matter 是否完整。LLM 失败不会阻止确定性 review 继续运行,但会在报告 warnings 中记录。
cmd/gcr/main.go CLI 入口
internal/review 确定性规则和 LLM review
internal/skill Skill 解析、语言识别和路由结果过滤
internal/rules 确定性规则配置
internal/constraints 多层约束加载
internal/platform/gitlab GitLab inline/summary 发布
internal/platform/github GitHub summary 发布
internal/report JSON/Markdown 报告
examples/gitlab/.gitlab-ci.yml GitLab CI 示例
examples/github/code-review.yml GitHub Actions 示例
scripts/build-release.sh 跨平台二进制构建和 checksum
.github/workflows/release.yml tag release workflow
- Skill 已支持仓库、全局、企业、个人、企业项目和项目层级合并;同名 Skill 由高优先级层覆盖。
- GitLab CI 支持通过
GCR_BINARY_URL下载独立二进制,也兼容源码构建。 - GitHub 已支持 Pull Request Reviews API 的行级 comment,并在失败时回退 summary。
- 当前规则配置使用 JSON;Semgrep 和 npm audit 支持 JSON 输出解析。其他扫描器和平台仍需增加对应适配器。
