From 03a7d98669118ec05f3a90cf6c2a4353ee5ddd85 Mon Sep 17 00:00:00 2001 From: Origami404 Date: Wed, 8 Apr 2026 22:32:01 +0800 Subject: [PATCH] docs: add gitea-pr-review design spec --- .../2026-04-08-gitea-pr-review-design.md | 302 ++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-08-gitea-pr-review-design.md diff --git a/docs/superpowers/specs/2026-04-08-gitea-pr-review-design.md b/docs/superpowers/specs/2026-04-08-gitea-pr-review-design.md new file mode 100644 index 0000000..36e562e --- /dev/null +++ b/docs/superpowers/specs/2026-04-08-gitea-pr-review-design.md @@ -0,0 +1,302 @@ +# 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 +# `#` + +<基本信息区> + +## Commits +- (<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。