172 lines
7.9 KiB
Markdown
172 lines
7.9 KiB
Markdown
# §23 — 工具链事件中枢补全:PR 全生命周期通知
|
||
|
||
> 状态:草案,待评审
|
||
> 作者:庞统
|
||
> 日期:2026-06-11
|
||
> 框架:基于 §20 Task Type Architecture + §13 工具链设计
|
||
|
||
## 背景
|
||
|
||
### 问题
|
||
|
||
工具链事件中枢(`toolchain_routes.py`)当前只覆盖了 PR 生命周期中约一半的交互节点。review 驳回后 PR 作者修改代码,没有机制通知 reviewer 重新 review——流程在这里断链。
|
||
|
||
### 当前覆盖
|
||
|
||
| 事件节点 | handler | 模板 | 状态 |
|
||
|---|---|---|---|
|
||
| PR 创建 → 通知 reviewer | `_handle_pull_request` (opened) | `review_request.md` | ✅ |
|
||
| Review 通过 → 通知 PR 作者 | `_handle_pull_request_review` (APPROVED) | `review_result.md` | ✅ |
|
||
| Review 驳回 → 通知 PR 作者 | `_handle_pull_request_review` (REQUEST_CHANGES) | `review_result.md` | ✅ |
|
||
| Issue 指派 → 通知被指派人 | `_handle_issues` (assigned) | `issue_assigned.md` | ✅ |
|
||
| CI 失败评论 → 通知 | `_handle_issue_comment` ([CI]) | `ci_failure.md` | ✅ |
|
||
| 部署失败 Issue → 通知 | `_handle_issues` (opened+"部署失败") | `deploy_failure.md` | ✅ |
|
||
|
||
### 缺失节点
|
||
|
||
| # | 事件节点 | Gitea 事件 | 优先级 | 理由 |
|
||
|---|---|---|---|---|
|
||
| E1 | PR 更新(push 新 commit)→ 通知 reviewer | `pull_request.synchronize` | **高** | review 驳回→修改→重 review 的关键闭环 |
|
||
| ~~E2~~ | ~~PR 合并通知~~ | ~~已删除~~ | ~~—~~ | ~~和 §22 CD 成功通知重叠,已删~~ |
|
||
| E2 | PR 合并 → 通知 PR 作者 | `pull_request` (closed+merged) | **高** | PR #38 恢复:CD 通知语义不同(部署状态 vs 合并信息),文档 PR 无 CD 流程仍需通知 |
|
||
| E3 | Review 评论(COMMENTED)→ 通知 PR 作者 | `pull_request_review` (COMMENTED) | 中 | reviewer 讨论提问,作者应知道 |
|
||
| E4 | PR 上普通评论 → 通知相关人 | `issue_comment` (on PR) | 低 | 非关键路径 |
|
||
|
||
## 方案
|
||
|
||
### 框架对齐
|
||
|
||
按 §20 Task Type Architecture,新增事件处理遵循:
|
||
1. `_EVENT_HANDLERS` 映射 → 路由到对应 handler 函数
|
||
2. handler 提取变量 → `render_template()` 渲染模板
|
||
3. `_TEMPLATE_MAP` 注册模板名 → `templates/toolchain/` 下新建模板文件
|
||
4. 通知目标通过 Gitea username → `to_agent_id()` 映射
|
||
|
||
### 新增 Handler 1:`_handle_pull_request_synchronize`
|
||
|
||
**触发**:`pull_request` 事件 + `action=synchronize`(PR 分支有新 push)
|
||
|
||
**通知对象**:PR 的 reviewer(从 PR 的 `requested_reviewers` 或最近一次 non-COMMENTED review 的提交者)
|
||
|
||
**实现**:
|
||
|
||
修改 `_handle_pull_request` 的 action 过滤,从只处理 `opened` 扩展为同时处理 `synchronize`:
|
||
|
||
```python
|
||
async def _handle_pull_request(payload: Dict[str, Any]) -> None:
|
||
action = payload.get("action", "")
|
||
if action == "opened":
|
||
await _handle_pr_opened(payload)
|
||
elif action == "synchronize":
|
||
await _handle_pr_synchronize(payload)
|
||
# 其他 action 忽略
|
||
```
|
||
|
||
新增 `_handle_pr_synchronize`:
|
||
1. 从 payload 取 PR 信息(number、title、author、head sha)
|
||
2. 查询最近一次 review(Gitea API `GET /repos/{owner}/{repo}/pulls/{number}/reviews`)取 reviewer
|
||
3. 如果没有 review 记录(`_fetch_latest_reviewer()` 返回 None),fallback 到默认 reviewer `simayi-challenger`,而非跳过通知(PR #38 改动:确保无 review 历史时也能通知默认审查者)
|
||
4. 渲染 `review_updated.md` 模板,发送 Mail 给 reviewer
|
||
|
||
**关键设计决策**:
|
||
- 不用 `requested_reviewers`(可能为空),用最近 review 的提交者
|
||
- 无 review 历史时 fallback 到默认 reviewer `simayi-challenger`(PR #38:避免 opened + synchronize 间隔较短时 reviewer 未收到任何通知)
|
||
- Mail from 用 `system`
|
||
|
||
### Handler 2:`_handle_pr_closed`(PR 合并通知)— PR #38 恢复
|
||
|
||
**触发**:`pull_request` 事件 + `action=closed` + `merged=true`
|
||
|
||
**通知对象**:PR 作者
|
||
|
||
**实现**:
|
||
|
||
修改 `_handle_pull_request` 的 action 分发,新增 `closed` 分支:
|
||
|
||
```python
|
||
async def _handle_pull_request(payload: Dict[str, Any]) -> None:
|
||
action = payload.get("action", "")
|
||
if action == "opened":
|
||
await _handle_pr_opened(payload)
|
||
elif action == "synchronize":
|
||
await _handle_pr_synchronize(payload)
|
||
elif action == "closed" and payload.get("pull_request", {}).get("merged"):
|
||
await _handle_pr_closed(payload)
|
||
# 其他 action 忽略
|
||
```
|
||
|
||
新增 `_handle_pr_closed`:
|
||
1. 从 payload 取 PR 信息(number、title、merged_by)
|
||
2. `merged_by` 优先从 `payload["pull_request"]["merged_by"]` 取,若为空则 fallback 到 `payload["sender"]`(PR #38:兼容不同 Gitea 版本和 merge 方式)
|
||
3. 渲染 `review_merged.md` 模板,发送 Mail 给 PR 作者
|
||
|
||
**恢复说明**:此前因与 §22 CD 成功通知重叠而删除。但实际场景中 CD 通知发的是部署状态,PR 作者更关心的是"谁帮我 merge 了"这个信息,两者语义不同。且 CD 流程不一定每次都触发(如文档 PR),merge 通知仍需独立存在。(PR #38 恢复)
|
||
|
||
### 新增 Handler 3:review COMMENTED 处理
|
||
|
||
**触发**:`pull_request_review` 事件 + `state=COMMENTED`
|
||
|
||
**通知对象**:PR 作者(不是 reviewer)
|
||
|
||
**实现**:
|
||
|
||
修改现有 `_handle_pull_request_review`,当前逻辑是"非 COMMENTED 才通知",改为 COMMENTED 也通知,但用不同模板:
|
||
|
||
```python
|
||
# 现有逻辑:非 COMMENTED 通知 PR 作者
|
||
if state in ("APPROVED", "REQUEST_CHANGES"):
|
||
template_name = "review_result"
|
||
elif state == "COMMENTED":
|
||
template_name = "review_comment"
|
||
else:
|
||
return # PENDING 等忽略
|
||
```
|
||
|
||
### 新增模板
|
||
|
||
| 模板文件 | 变量 | 说明 |
|
||
|---|---|---|
|
||
| `review_updated.md` | repo, pr_number, pr_title, pr_author, branch, new_sha, reviewer | PR 有新 commit,请重新 review |
|
||
| `review_merged.md` | repo, pr_number, pr_title, pr_author, merged_by | PR 已合并,通知作者(PR #38 恢复) |
|
||
| `review_comment.md` | repo, pr_number, pr_title, reviewer, comment_body | reviewer 提交了评论 |
|
||
|
||
### `_EVENT_HANDLERS` 无需改动
|
||
|
||
`synchronize` 和 `closed` 都是 `pull_request` 事件的 action 子类型,已映射到 `_handle_pull_request`。COMMENTED 是 `pull_request_review` 的 state 子类型,已映射到 `_handle_pull_request_review`。
|
||
|
||
所以 **`_EVENT_HANDLERS` 不需要修改**,只需修改 handler 内部的 action/state 分发逻辑。
|
||
|
||
### 不做的事
|
||
|
||
| 项 | 理由 |
|
||
|---|---|
|
||
| E4 PR 上普通评论通知 | 低优,非关键路径,后续按需加 |
|
||
| Issue 关闭通知 | 低优,关怀性质 |
|
||
| reviewer 从 `requested_reviewers` 取 | 不可靠(可能为空),用最近 review 记录更稳定 |
|
||
|
||
## 改动范围
|
||
|
||
| 文件 | 改动 |
|
||
|---|---|
|
||
| `src/api/toolchain_routes.py` | 修改 `_handle_pull_request`(扩展 action 分发 + closed 分支)+ 新增 `_handle_pr_synchronize` + `_handle_pr_closed` + 修改 `_handle_pull_request_review`(支持 COMMENTED) |
|
||
| `templates/toolchain/review_updated.md` | 新增 |
|
||
| `templates/toolchain/review_merged.md` | 新增(PR #38 恢复) |
|
||
| `templates/toolchain/review_comment.md` | 新增 |
|
||
| `src/daemon/toolchain_templates.py` | `_TEMPLATE_MAP` 新增 3 个映射 |
|
||
| `docs/design/23-toolchain-pr-lifecycle.md` | 本文档 |
|
||
|
||
## 验证计划
|
||
|
||
在 `sanguo/moziplus-v2` 测试仓库上 E2E 验证:
|
||
|
||
1. **synchronize**:创建 PR → review 驳回 → push 新 commit → 验证 reviewer 收到"请重新 review" Mail
|
||
2. **synchronize fallback**(PR #38):创建 PR → push commit(无 review 历史)→ 验证默认 reviewer (`simayi-challenger`) 收到通知
|
||
3. **merge 通知**(PR #38 恢复):PR merge → 验证 PR 作者收到合并通知 Mail
|
||
4. **COMMENTED**:review 提交纯评论 → 验证 PR 作者收到通知
|
||
|
||
## 风险评估
|
||
|
||
- **风险等级**:低。新增事件处理,不修改现有 handler 逻辑
|
||
- **幂等性**:复用现有 `_is_duplicate` 机制
|
||
- **性能**:synchronize handler 有一次 Gitea API 调用(查 review 历史),频率低(只在 push 后触发)
|