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

8.7 KiB
Raw Blame History

gitea-pr-review 设计文档

  • 日期: 2026-04-08
  • 项目: gitea-pr-review
  • 目标: 读取 Gitea PR 信息,输出 LLM 友好的 MarkdownJSON

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 核心数据模型(内部)

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[]
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 顶层结构

# <repo> `#<pr-index>` <pr-title>

<pr 描述>

## Metadata

### Commits
- <sha-short> <title> (<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 输出规范

输出为单对象,建议结构如下:

{
  "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。