Files
gitea-pr-review/docs/superpowers/specs/2026-04-08-gitea-pr-review-design.md
T

303 lines
7.6 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-pr-review 设计文档
- 日期: 2026-04-08
- 项目: `gitea-pr-review`
- 目标: 读取 Gitea PR 信息,输出 LLM 友好的 `Markdown``JSON`
## 1. 背景与目标
当前需求是将单个 PR 的评审上下文整理成可直接喂给在同仓库工作的 LLM 的输入文档。重点是:
1. 评论与回复内容完整保留(最高优先级)。
2. 保留基础 PR 元信息,尤其是分支名与 commit 列表。
3. 提供基础 diff stat(不含完整 diff 内容)。
4. 输出两种格式:
- `markdown`: 直接阅读与投喂 LLM。
- `json`: 结构化保真输出,便于脚本和后续处理。
## 2. 范围
### 2.1 In Scope
1. 输入 `pr_index`,通过 Gitea API 拉取:
- PR 基本信息(标题、作者、状态、源/目标分支、时间)
- review 列表
- review comments(含回复)
- commit 列表
- diff stat(文件级增删统计或可等价聚合的数据)
2. 生成 Markdown 输出,结构遵循 `README.md` 约定。
3. 生成 JSON 输出,字段可追溯到源 API 实体。
4. 处理评论正文为 Markdown 的情况,避免破坏外层文档结构。
### 2.2 Out of Scope
1. 不输出完整 diff patch。
2. 不做评论摘要、情感分析、优先级排序等“解释型加工”。
3. 不做跨 PR 聚合。
## 3. 方案对比与结论
### 方案 A: 拉取后直接拼 Markdown
- 优点: 最快。
- 缺点: 结构易耦合;JSON 输出扩展困难;线程关系处理易混乱。
### 方案 B: 标准化模型 + 多格式渲染(推荐)
- 流程: `fetch -> normalize -> render(markdown/json)`
- 优点:
1. 采集与展示解耦。
2. 评论线程完整性规则可集中实现。
3. 便于新增输出格式或字段。
- 缺点: 初期代码比 A 多一层。
### 方案 C: 模板引擎渲染
- 优点: 视觉格式更可配置。
- 缺点: 对当前固定结构需求偏重,收益不高。
### 结论
采用方案 B,优先保证数据完整性与可追溯性,再做格式输出。
## 4. 架构设计
### 4.1 模块划分
1. `cli`
- 解析参数与环境变量。
- 决定输出格式与输出路径。
2. `gitea_client`
- 仅负责 API 调用与分页。
- 返回原始 DTO(与 API 字段接近)。
3. `normalize`
- 将原始 DTO 归一化为内部模型 `PrReviewDocument`
- 负责线程聚合、排序、缺失值兜底。
4. `render_markdown`
-`PrReviewDocument` 渲染为 Markdown。
5. `render_json`
-`PrReviewDocument` 序列化为稳定 JSON。
### 4.2 核心数据模型(内部)
```text
PrReviewDocument
meta:
repo, pr_index, pr_title, pr_state, author
base_branch, head_branch
created_at, updated_at, merged_at?
commits: CommitItem[]
diff_stat:
files_changed, additions, deletions
files?: FileStat[]
reviews: ReviewItem[]
threads: CommentThread[]
```
```text
CommentThread
thread_id
file_path?
line?
root_comment: CommentItem
replies: CommentItem[]
```
`CommentItem.body` 保存原始文本,不做语义改写。
## 5. 数据流与排序规则
1. 读取输入:
- `pr_index` 来自命令行。
- `repo/url/token` 来自环境变量或参数。
2. 拉取数据:
- PR 基本信息
- reviews
- comments(含回复关系字段)
- commits
- diff stat
3. 归一化:
- 基于 `in_reply_to` / `original_id` 等字段聚合线程。
- 无父评论但标记为回复的异常数据,单独作为孤立线程并记录 warning。
- 统一按时间排序:
- 线程按根评论时间升序。
- 回复按时间升序。
4. 渲染:
- Markdown: 面向阅读。
- JSON: 面向程序消费。
## 6. Markdown 输出规范
目标: 对齐 `README.md` 给出的基础结构,保证评论内容完整。
### 6.1 顶层结构
````md
# <repo> `#<pr-index>` <pr-title>
<基本信息区>
## Commits
- <sha-short> <title> (<author>, <date>)
...
## Diff Stat
- files changed: <n>
- additions: <n>
- deletions: <n>
## Review 1 (<review-state>)
> <reviewer>
### Comment 1.1
<file>:<line>
<user>:
```md
<原始评论正文>
```
### Reply 1.1.1
<user>:
```md
<原始回复正文>
```
````
### 6.2 Markdown 正文保真策略
评论正文可能本身含 Markdown(标题、列表、代码块、引用等),为避免破坏外层结构,统一策略:
1. 评论正文使用 fenced code block 包裹,info string 为 `md`。
2. 若正文内已含三反引号,动态选择更长 fence(例如四反引号)。
3. 不转义正文中的 Markdown 语法,不改写文本。
## 7. JSON 输出规范
输出为单对象,建议结构如下:
```json
{
"meta": {
"repo": "Origami404/aaa",
"pr_index": 123,
"title": "feat: ...",
"state": "open",
"author": "alice",
"base_branch": "main",
"head_branch": "feature/x",
"created_at": "2026-04-07T10:00:00Z",
"updated_at": "2026-04-08T10:00:00Z",
"merged_at": null
},
"commits": [
{
"sha": "abcdef...",
"short_sha": "abcdef1",
"title": "fix: ...",
"author": "bob",
"date": "2026-04-07T12:00:00Z"
}
],
"diff_stat": {
"files_changed": 3,
"additions": 120,
"deletions": 30,
"files": [
{"path": "src/a.rs", "additions": 10, "deletions": 2}
]
},
"reviews": [
{
"id": 1,
"state": "COMMENT",
"reviewer": "carol",
"submitted_at": "2026-04-08T08:00:00Z"
}
],
"threads": [
{
"thread_id": "t-1",
"file_path": "src/main.rs",
"line": 42,
"root_comment": {
"id": 11,
"user": "carol",
"created_at": "2026-04-08T08:01:00Z",
"body": "原始 markdown 文本"
},
"replies": [
{
"id": 12,
"user": "alice",
"created_at": "2026-04-08T08:02:00Z",
"body": "原始 markdown 文本"
}
]
}
]
}
```
约束:
1. `body` 保留原始字符串。
2. 数组顺序稳定(按时间/出现顺序规则)。
3. 字段缺失时使用 `null` 或空数组,不省略关键键。
## 8. CLI 与配置
### 8.1 输入
1. 必填:
- `pr_index`(位置参数)
2. 环境变量(可被参数覆盖):
- `GITEA_PR_CLI_API_TOKEN`
- `GITEA_PR_CLI_URL`
- `GITEA_PR_CLI_REPO`
### 8.2 输出参数(新增)
1. `--format markdown|json|both`(默认 `markdown`
2. `--out <path>`(单输出文件)
3. `--out-dir <dir>``both` 时用于分文件输出)
## 9. 错误处理策略
1. 配置错误: 缺 token/repo/url,直接报错并退出非 0。
2. 网络/API 错误: 明确打印 HTTP 状态码与接口路径。
3. 数据不完整:
- 评论缺父节点: 降级为孤立线程并 warning。
- 分支或 commit 字段缺失: 输出 `null` 并 warning。
4. 渲染错误: 指出是 Markdown 还是 JSON 渲染失败。
## 10. 测试策略
1. 单元测试
- 线程聚合(正常链路、孤立回复、乱序输入)。
- Markdown fence 选择逻辑(三反引号冲突场景)。
- JSON 序列化稳定性(字段与顺序)。
2. 快照测试
- 固定输入 DTO -> Markdown 输出快照。
3. 集成测试
- 使用 mock server 覆盖分页、鉴权失败、空评论 PR。
## 11. 里程碑计划(高层)
1. M1: 完成 `gitea_client` + DTO。
2. M2: 完成 `normalize` 与线程规则。
3. M3: 完成 Markdown 渲染并对齐 README 结构。
4. M4: 完成 JSON 输出。
5. M5: 补齐测试与错误处理。
## 12. 验收标准
1. 能对任意有效 PR index 输出 Markdown,且包含:
- 基础信息
- 分支名
- commit 列表
- diff stat
- review/comment/reply 完整正文
2. 能输出 JSON 且字段完整、顺序稳定。
3. 评论正文含 Markdown/代码块时,Markdown 总文档结构不被破坏。
4. 关键异常路径有清晰错误信息并返回非 0。