# 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 的情况,避免破坏外层文档结构。 5. 提供 `json -> markdown` 子命令,从 JSON 文件渲染出与主流程一致的 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` - 解析参数与环境变量。 - 处理子命令分发与输出目标(默认 stdout)。 2. `gitea_client` - 仅负责 API 调用与分页。 - 返回原始 DTO(与 API 字段接近)。 3. `normalize` - 将原始 DTO 归一化为内部模型 `PrReviewDocument`。 - 负责线程聚合、排序、缺失值兜底。 4. `render_markdown` - 将 `PrReviewDocument` 渲染为 Markdown。 5. `render_json` - 将 `PrReviewDocument` 序列化为稳定 JSON。 6. `input_json` - 从 JSON 文件反序列化为 `PrReviewDocument`。 ### 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: 面向程序消费。 ### 5.1 子命令数据流 1. `fetch` 子命令(主流程): - `fetch -> normalize -> render(markdown|json) -> stdout/--out` 2. `render-md` 子命令: - `read json file -> deserialize(PrReviewDocument) -> render(markdown) -> stdout/--out` ## 6. Markdown 输出规范 目标: 对齐 `README.md` 给出的基础结构,保证评论内容完整。 ### 6.1 顶层结构 ````md # `#` ## Metadata ### Commits - (<author>, <date>) ... ### Diff Stat total: <files_changed> files, +<additions>, -<deletions> - <file-path>: +<additions>, -<deletions> ... ## 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. `fetch <pr_index>` - 从 Gitea 拉取并输出文档。 2. `render-md --in <json_path>` - 从 JSON 输入渲染 Markdown。 ### 8.2 输入配置(`fetch`) 1. 必填: - `pr_index`(位置参数) 2. 环境变量(可被参数覆盖): - `GITEA_PR_CLI_API_TOKEN` - `GITEA_PR_CLI_URL` - `GITEA_PR_CLI_REPO` ### 8.3 输出参数 1. `--format markdown|json`(默认 `markdown`,仅 `fetch` 支持) 2. `--out <path>`(可选;未提供时默认输出到 stdout) 约束: 1. 移除 `both` 选项。 2. 移除 `--out-dir` 参数。 ## 9. 错误处理策略 1. 配置错误: 缺 token/repo/url,直接报错并退出非 0。 2. 网络/API 错误: 明确打印 HTTP 状态码与接口路径。 3. 数据不完整: - 评论缺父节点: 降级为孤立线程并 warning。 - 分支或 commit 字段缺失: 输出 `null` 并 warning。 4. 渲染错误: 指出是 Markdown 还是 JSON 渲染失败。 5. `render-md` 输入错误: - JSON 文件不存在/不可读时,返回非 0 并指明路径。 - JSON schema 不匹配时,定位字段并返回非 0。 ## 10. 测试策略 1. 单元测试 - 线程聚合(正常链路、孤立回复、乱序输入)。 - Markdown fence 选择逻辑(三反引号冲突场景)。 - JSON 序列化稳定性(字段与顺序)。 - `render-md` 反序列化与渲染一致性。 2. 快照测试 - 固定输入 DTO -> Markdown 输出快照。 3. 集成测试 - 使用 mock server 覆盖分页、鉴权失败、空评论 PR。 ## 11. 里程碑计划(高层) 1. M1: 完成 `gitea_client` + DTO。 2. M2: 完成 `normalize` 与线程规则。 3. M3: 完成 Markdown 渲染并对齐 README 结构。 4. M4: 完成 JSON 输出与 `render-md` 子命令。 5. M5: 补齐测试与错误处理。 ## 12. 验收标准 1. 能对任意有效 PR index 输出 Markdown,且包含: - 基础信息 - 分支名 - commit 列表 - diff stat - review/comment/reply 完整正文 2. 能输出 JSON 且字段完整、顺序稳定。 3. `render-md --in <json_path>` 能从 JSON 成功产出 Markdown,且结构与 `fetch --format markdown` 一致。 4. 评论正文含 Markdown/代码块时,Markdown 总文档结构不被破坏。 5. 关键异常路径有清晰错误信息并返回非 0。