Files
git-codereview/README.md
T
kgod 6dc77ce907 feat: 仓库导入、分支模型重构与审查历史摘要
仓库接入
- 新增「从 Gitea 导入」:用全局 Token 列出可见仓库,一键建配置
- 新增仓库只填 Git 地址,自动解析 owner/name,支持 https/ssh/scp 写法
- 新增「测试连接」按钮,保存前即可校验地址并拉取分支

分支模型
- 由单一 base_branch + glob 改为「一个管理分支 + 多个检查分支」
- 管理分支是唯一合并目标;检查分支全部纳入监控
- 支持从任意分支克隆创建管理分支
- 审查基准改为管理分支的 merge-base;仅目标为管理分支的 PR 才自动合并
- 旧库自动迁移:base_branch 播种 managed_branch,branch_patterns 展开为检查分支

审查历史
- 新增 review_records 表与「审查历史」页
- 记录触发来源、PR 链接、审查范围、阻断阈值、发布方式、自动合并设置、
  排除路径、LLM 模型、token 消耗、耗时、需求背景与全部审查意见
- 详情弹窗一览,支持按仓库过滤

AI 摘要
- 新增独立摘要模块(app/lib/summary.js),与代码审查提示词分离
- 专用提示词输出固定四节、500 字内的中文记录:结论/范围/问题/要点
- 与代码审查共用全局 LLM 设置;摘要失败不影响审查与合并,可单条重跑

修复
- 摘要改用内置 fetch:运行镜像没有 curl,原先 spawn curl 必然 ENOENT
- 去掉 blob:none 部分克隆并把凭据写入 .git/config:
  惰性取 blob 不会带上 per-command extraHeader,私有库会报 could not read Username

UI
- 审查背景改为多行文本域(可滚动)
- 分支改为可点选列表,管理分支高亮
- 仓库表格展示管理分支与检查分支
2026-09-20 12:38:23 +08:00

257 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gitea 自动代码审查
基于 [OpenCodeReview](https://github.com/alibaba/open-code-review)(OCR)的 Gitea 自动代码审查服务。
监听仓库推送和 Pull Request,调用 OCR 分析 diff,把结果发布回 Gitea,并按规则决定是否自动合并。
## 它做什么
```text
Push / Pull Request 事件
↓ Gitea Webhook(HMAC 签名)
gitea-codereview
↓ git fetch + OCR 审查
OpenCodeReview CLI → LLM
↓ 结构化 JSON(文件 / 行号 / 类别 / 严重级别)
Gitea:内联评论 + 汇总评论 + Issue + 提交状态
↓ 无阻断问题且开启自动合并
自动合并 / 等待检查通过后合并
```
## 功能
**仓库接入**
- 「从 Gitea 导入」一键列出全局 Token 可见的仓库,点「添加」即建好监控配置
- 手动新增时只填 Git 地址,自动识别 `所有者/仓库名`,旁边可「测试连接」验证
- 支持 https / ssh / scp 三种地址写法
**分支模型**
- 一个「管理分支」作为唯一合并目标
- 多个「检查分支」全部纳入监控,点选即可增减
- 管理分支不存在时可「克隆创建」,从任意已有分支复制
- 审查始终以管理分支为对比基准,即「这些改动合入管理分支会怎样」
**审查触发**
- 监听检查分支的推送,对比管理分支的 merge-base
- 监听 Pull Request 的 opened / synchronize / reopened / ready_for_review
- 可选只审 PR 或只审 push
- 同一 commit 在队列中只入队一次;webhook 按 delivery id 去重
**发布结果**
- PR 内联评论,带 `[category · severity]` 标记和 `suggestion` 代码块
- 每条内联评论都校验是否落在本次 diff 的行上,落不到的汇总进审查总结
- 提交状态 `code-review/ocr`(success / failure / error),可配成必需检查
- 同一仓库同一分支只维护一个 Issue:有问题时创建或更新,修好后自动关闭
- 可配置阻断阈值:按严重级别、按类别,或任何意见都算
**自动合并**
- 仅在无阻断问题、审查覆盖完整、提交状态为 success 时才触发
- 两种模式:`when_checks_succeed`(等分支保护检查通过)和 `immediate`
- 合并方式、是否删除分支可配
- PR head 已变化或 PR 不可合并时自动放弃
**审查历史**
- 「审查历史」页按仓库汇总每次审查的摘要,可一键跳转 PR
- 每条记录保留完整上下文:触发来源、PR 链接、审查范围、阻断阈值、发布方式、
自动合并设置、排除路径、LLM 模型、token 消耗、耗时、本次填写的需求背景
- 详情弹窗列出本次全部审查意见,标注严重级别与是否阻断
**AI 摘要**
- 独立的摘要模块,与代码审查的提示词完全分开,但共用同一份全局 LLM 设置
- 专用提示词把一次审查压缩成固定四节、500 字内的记录:
`结论 / 范围 / 问题 / 要点`
- 摘要失败不影响审查结果,可单条重新生成
**运维**
- 内置 Web 后台:仓库配置、审查历史、任务列表、实时日志、重跑、连通性自检
- 每次审查的完整日志和结果 JSON 落库
- worker 重启后自动回收卡住的任务,失败任务自动重试一次
- 全局或按仓库覆盖 Gitea token、LLM 端点 / 模型 / Key、排除路径、并发数
## 目录
```text
gitea-codereview/
├── app/
│ ├── server.js HTTP 服务:webhook、REST API、静态后台
│ ├── lib/
│ │ ├── db.js SQLite 结构与查询
│ │ ├── gitea.js Gitea REST 客户端
│ │ ├── ocr.js 调用 OCR CLI,解析 JSON
│ │ ├── diff.js 解析 unified diff,定位内联评论锚点
│ │ ├── review.js 审查流水线:发布、Issue、提交状态、自动合并
│ │ └── queue.js 串行任务队列
│ └── static/ 后台前端
├── tests/core.test.js
├── Dockerfile
└── docker-compose.yml
```
## 部署
### 1. 准备 Gitea
**创建访问 Token**:Gitea → 用户设置 → 应用 → 生成令牌,勾选 `repo` 与 `issue` 权限。
**允许 Gitea 向内网投递 Webhook**(Gitea 默认拦截内网地址)。在 Gitea 的 `app.ini` 或容器环境变量中加:
```yaml
GITEA__webhook__ALLOWED_HOST_LIST: "192.168.31.51"
```
多个地址用逗号分隔,也可写 CIDR(`192.168.31.0/24`)或内置名(`private`、`loopback`)。
### 2. 准备 LLM 端点
OCR 需要一个 OpenAI 兼容或 Anthropic 兼容的接口,填进 `.env` 的 `CR_LLM_URL` / `CR_LLM_TOKEN` / `CR_LLM_MODEL`。
用 `CR_LLM_PROTOCOL=openai` 或 `anthropic` 指定协议。
### 3. 配置并启动
```bash
cd gitea-codereview
cp .env.example .env
# 填入 CR_GITEA_TOKEN、CR_WEBHOOK_SECRET、CR_ADMIN_TOKEN、CR_LLM_*
docker compose build
docker compose up -d
docker compose ps
curl -fsS http://localhost:8090/api/health
```
打开后台 `http://<服务器>:8090`:
0. 如果设置了 `CR_ADMIN_TOKEN`,页面会先要求输入该 Token(保存在浏览器本地,可随时「清除访问 Token」)
1. 在「设置」里确认 Gitea 地址、LLM 端点,点「测试 Gitea」和「测试 LLM」验证连通
2. 在「仓库」里新增要审查的仓库,配置分支过滤、阻断阈值、是否自动合并
3. 在 Gitea 仓库 Settings → Webhooks 添加 Webhook:
- 目标 URL:`http://<服务器>:8090/webhook/gitea`
- 内容类型:`application/json`
- 密钥:填 `.env` 里的 `CR_WEBHOOK_SECRET`
- 事件:Push 与 Pull Request
也可以只配 `.env` 不打开后台;后台的修改会持久化到 SQLite 卷。
## 配置项
### 全局(`.env` 或后台「设置」)
| 变量 | 说明 |
| --- | --- |
| `CR_GITEA_URL` | Gitea 根地址,如 `http://192.168.31.51` |
| `CR_GITEA_TOKEN` | 发布评论、Issue、提交状态用的 Token |
| `CR_WEBHOOK_SECRET` | Webhook HMAC 密钥;设置后拒绝签名不符的请求 |
| `CR_ADMIN_TOKEN` | 保护后台和 REST API 的 Bearer Token;设置后打开后台需先输入;留空则不校验 |
| `CR_LLM_URL` / `CR_LLM_TOKEN` / `CR_LLM_MODEL` | LLM 端点与凭据 |
| `CR_LLM_PROTOCOL` | `openai` 或 `anthropic` |
| `CR_LLM_AUTH_HEADER` | 自定义认证头名,默认由协议决定(如 `x-api-key`) |
| `CR_LLM_EXTRA_HEADERS` | 附加请求头,`K=V,K=V` |
| `CR_LLM_TIMEOUT` | 单次 LLM 请求超时秒数,默认 `180` |
| `CR_RULE_PATH` | 自定义 OCR 规则 JSON 的容器内路径 |
| `CR_HOST_PORT` | 后台映射到宿主机的端口,默认 `8090` |
| `CR_CONCURRENCY` | 单个审查任务的并发文件数 |
| `CR_REVIEW_TIMEOUT_MS` | 单次审查超时,默认 45 分钟 |
| `CR_MAX_TOKENS_BUDGET` | 单次审查 token 上限,`0` 为不限 |
| `CR_OCR_COMMAND` | OCR 可执行文件名,默认 `ocr` |
| `CR_DATA_DIR` / `CR_DB_PATH` | 数据目录与 SQLite 路径,默认容器内 `/data` |
| `CR_POLL_MS` / `CR_MAX_ATTEMPTS` | 队列轮询间隔与失败重试次数 |
### 按仓库(后台「仓库」)
| 字段 | 说明 |
| --- | --- |
| `repo_url` | 仓库 Git 地址,`owner` / `name` 由此自动解析 |
| `managed_branch` | 唯一的管理分支,所有审查都相对它做对比,也是唯一允许自动合并的目标 |
| `check_branches` | 纳入监控的分支列表,逗号分隔;支持任意多个 |
| `review_scope` | `both` / `pr` / `push` |
| `block_severity` | 阻断级别阈值,如 `critical,high`;留空则不看级别 |
| `block_categories` | 额外按类别阻断,如 `security` |
| `fail_on_findings` | 打开后任何意见都视为阻断 |
| `auto_merge` | 无阻断问题时自动合并 |
| `auto_merge_mode` | `when_checks_succeed` / `immediate` |
| `merge_method` | `squash` / `merge` / `rebase` / `rebase-merge` / `fast-forward-only` |
| `delete_branch` | 合并后删除源分支 |
| `publish_mode` | `inline`(PR 内联评论)/ `issue-only` |
| `create_issue` | 是否创建 / 更新 Issue |
| `issue_labels` | Issue 标签,不存在时自动创建 |
| `excludes` | OCR 排除路径,gitignore 风格,逗号分隔 |
| `max_comments` | 单次最多发布多少条意见 |
| `gitea_token` / `llm_token` / `llm_model` | 覆盖全局配置 |
## REST API
所有 `/api/*` 接口在设置 `CR_ADMIN_TOKEN` 后需要 `Authorization: Bearer <token>`。
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| `GET` | `/api/health` | 健康检查与队列统计(免鉴权) |
| `GET` `PUT` | `/api/settings` | 读写全局设置 |
| `GET` `POST` | `/api/repos` | 列出 / 新增仓库配置 |
| `GET` `PATCH` `DELETE` | `/api/repos/:id` | 读取 / 修改 / 删除 |
| `POST` | `/api/repos/:id/discover` | 校验连接,返回默认分支与分支列表 |
| `GET` | `/api/gitea/repos` | 列出全局 Token 可见的仓库,供一键导入 |
| `POST` | `/api/repos/parse` | 解析 Git 地址,返回 `owner` / `name` |
| `POST` | `/api/repos/branches` | 用 Git 地址查询分支(仓库尚未保存时使用) |
| `POST` | `/api/repos/:id/branches` | 创建分支,可从指定分支克隆 |
| `GET` | `/api/records` | 审查历史(`?repo_id=`、`?limit=`) |
| `GET` | `/api/records/:id` | 单条记录,含参数、意见与摘要 |
| `POST` | `/api/records/:id/summarise` | 重新排队生成摘要 |
| `GET` | `/api/jobs` | 任务列表(`?repo_id=`、`?limit=`) |
| `GET` | `/api/jobs/:id` | 任务详情,含日志 |
| `POST` | `/api/jobs/:id/retry` | 重跑任务 |
| `POST` | `/api/review` | 手动排队一次审查 |
| `POST` | `/api/gitea/test` | 测试 Gitea 连接 |
| `POST` | `/api/selftest` | 测试 OCR 与 LLM 连通性 |
手动触发一次审查:
```bash
curl -X POST http://localhost:8090/api/review \
-H "Authorization: Bearer $CR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"owner":"kgod","name":"myrepo","ref":"main","sha":"<commit-sha>"}'
```
## 行为说明
**分支与合并**:`check_branches` 里的每个分支都会被监控审查,审查基准是 `managed_branch`(用 merge-base 计算),也就是「这些改动合入管理分支会怎样」。自动合并只作用于目标为 `managed_branch` 的 PR,其他分支的 PR 只审不合。
**审查范围**:PR 事件优先用 PR 的目标分支作为基准;push 事件优先用 webhook 里的 `before`,当 `before` 是新建分支的全零值或缺失时,退回到与管理分支的 merge-base。
**审查背景**:仓库配置里的「审查背景 / 需求」是多行文本,既作为 OCR 的 `--background` 传给模型,也会完整记录到审查历史,方便回溯「当时是按什么需求审的」。
**阻断判定**:一条意见命中任一条件即为阻断 —— 严重级别 ≥ `block_severity` 中任一项,类别在 `block_categories` 中,或打开了 `fail_on_findings`。阻断会创建 Issue、把提交状态置为 `failure`,并阻止自动合并。
**审查不完整时**:OCR 报告 `partial` / `failed`,或存在文件级警告,或 token 预算被截断时,仍会发布已有意见,但不会自动合并,提交状态也不会是 success。宁可漏合,不可错合。
**Issue 生命周期**:标题格式 `[OCR] <owner>/<repo> · <branch> 存在阻断级代码问题`。同一分支再次出现阻断问题时更新该 Issue 并追加一条说明;该分支复查通过后自动评论并关闭。`create_issue` 关闭时不新建 Issue,但仍会关闭此前开过的。
**重复保护**:同一 commit 已在队列中或正在审查时不重复入队;Gitea 重投的同一 delivery id 会被忽略。
## 开发
```bash
node --test tests/core.test.js # 单元测试
node app/server.js # 本地直接运行(需先装 ocr CLI)
docker compose build # 构建镜像
```
本地运行需要 Node ≥ 22.5(用到内置 `node:sqlite`)、`git` ≥ 2.41,以及 `npm i -g @alibaba-group/open-code-review`。
## 故障排查
**Webhook 投递失败,提示 `webhook can only call allowed HTTP servers`**
Gitea 拦截了内网地址。按上文给 `[webhook] ALLOWED_HOST_LIST` 加上本服务地址,重启 Gitea。
**后台打开后要求输入访问 Token**
这是 `CR_ADMIN_TOKEN` 在生效。输入 `.env` 里的值即可,Token 只存在浏览器本地。想免登录就把它留空并重启容器(仅限不对外暴露的网络)。
**历史记录一直显示「摘要生成中…」**
摘要与审查串行执行,一次只跑一个;如果 LLM 不可达会在记录里留下失败原因,可在详情里点「重新生成摘要」。摘要失败不会影响审查结论与自动合并。
**任务一直停在 `reviewing`**
LLM 慢或不可达。在后台「设置」点「测试 LLM」,或在「任务」页查看日志;调大 `CR_REVIEW_TIMEOUT_MS`。
**内联评论变成了汇总里的条目**
OCR 给的行号不在本次 diff 内(常见于问题定位到未改动的上下文行)。服务会保留意见并汇总展示,不丢结果。
**自动合并没有发生**
看任务日志的 `auto-merge skipped` 原因:存在阻断问题、审查覆盖不完整、提交状态非 success、PR head 已变化或 PR 不可合并。Gitea 侧还需该 Token 有合并权限。