Files
sanguo_moziplus_v2/docs/design/13-toolchain-and-dev-workflow.md
T
cfdaily 4d81e02cf2
Deploy / ci (push) Waiting to run
Deploy / deploy (push) Blocked by required conditions
Deploy / notify-deploy-failure (push) Blocked by required conditions
auto-sync: 2026-06-07 11:18:00
2026-06-07 11:18:00 +08:00

2043 lines
87 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.
# 三国团队工具链与开发流程设计
> **状态**: v2.1 — 事件中枢详细设计(§16 新增)
> **作者**: 庞统(副军师)🐦
> **评审**: 司马懿(仲达)🗡️
> **日期**: 2026-06-06
> **v0.4 变更**: 主公 6 条反馈(需求review前置、E2E用户触发、测试分工、审查默认司马懿、项目维度组织、分支保护说明)
> **v0.5 变更**: E2E 改为自动跑(CI隔离环境不污染生产)、三环境架构、schema变更检查确认、分支保护确认开启
> **v0.6 变更**: Agent spawn 走生产 openclaw(不隔离)、程序逻辑走 CI 隔离环境、路径硬编码修复清单
> **定位**: 普适工具链和开发流程,适用于三国团队所有项目,与 moziplus 2.0 解耦
---
## §1. 设计原则
| # | 原则 | 说明 |
|---|------|------|
| 1 | **声明式 + 代码执行分离** | 规则声明在配置中,执行由框架代码保证,不依赖 Agent 自觉(来源: moziplus-quality-governance |
| 2 | **能自动就自动** | CI/CD/部署能自动触发就不手动,人只做决策和审查 |
| 3 | **Skill 固化流程** | 所有流程通过 Skill(提示词)固化,不靠口头约定 |
| 4 | **分级治理** | 按风险级别选择流程严格度,不搞一刀切(来源: quality-gate-patterns |
| 5 | **验证才算完** | Agent 说"完成"不算,CI 通过 + 产出物存在才算(来源: 幻觉门控) |
| 6 | **每个项目自带部署** | 部署脚本归项目管,工具链负责触发和验证,不替项目做部署决策 |
| 7 | **需求/问题必须经过 Review** | 所有需求和问题提交前必须经过 Review,不直接进入开发 |
| 8 | **项目维度组织** | 所有内容(Issue、CI、部署、Skill 配置)以项目为单位组织,支持多项目并行 |
---
## §2. 基础设施层
### 2.1 代码托管:Gitea(唯一)
| 项 | 配置 |
|----|------|
| 地址 | `http://192.168.2.154:3000` |
| 版本 | v1.23.4 |
| 认证 | HTTP + token(待配置) |
| 权限 | cfdaily 用户;姜维持有 admin 权限(启用 Actions、分支保护等) |
### 2.2 CI/CDGitea Actions
| 项 | 配置 |
|----|------|
| Runner | Mac mini 裸机,act-runnerGo 二进制) |
| 配置文件 | `.gitea/workflows/*.yml`,每个项目自管 |
| 语法 | 兼容 GitHub Actionsv1.23.4 已验证支持) |
| 触发 | push / PR / tag |
### 2.3 部署目标
| 环境 | 位置 | 说明 |
|------|------|------|
| Mac mini 本机 | `~/.sanguo_projects/<project>/` | 主力开发和运行环境 |
| NAS Docker | `192.168.2.154` | 部分服务(Gitea、回测等) |
---
## §3. 分支策略
### 3.1 采用 Trunk-Based Development(简化版)
选 Trunk-Based 而非 Git Flow 的理由:
- **团队小**(<10 人),不需要复杂的 release 分支
- **AI Agent 参与编码**Agent 在短生命周期 feature 分支上工作,完成后快速合并
- **CI 是安全网**main 分支始终可部署,feature 分支通过 CI 才能合并
前提条件:**Agent 必须知道当前所在分支**。git-workflow Skill 需注入 `git branch --show-current` 到 Agent 上下文(仲达 Q1 补充)。
### 3.2 分支规则
| 分支类型 | 命名 | 生命周期 | 合并方式 | 适用场景 |
|---------|------|---------|---------|---------|
| `main` | `main` | 永久 | — | 始终可部署 |
| feature | `feat/<issue-N>-<简述>` | <3天 | PR → CI → Review → merge | 新功能 |
| bugfix | `fix/<issue-N>-<简述>` | <1天 | PR → CI → merge | Bug 修复 |
| hotfix | `hotfix/<简述>` | <2小时 | 直接 push main + CI + 自动回滚 | 紧急修复 |
### 3.3 分支保护规则
- `main` 分支:
- 不允许直接 pushhotfix 除外)
- PR 必须通过 CI(至少 unit + integration tests
- PR 必须至少 1 人 Review(人或 Agent
- **hotfix 直接 push main 后必须触发 CI**(至少 lint + unit),CI 失败则自动 revertM1 修订)
- feature/bugfix 分支:
- 无保护,自由 push
- 合并前必须 CI 通过
### 3.4 Commit 规范
```
<type>(<scope>): <subject>
<body>
```
| type | 说明 |
|------|------|
| `feat` | 新功能 |
| `fix` | Bug 修复 |
| `refactor` | 重构(不改行为) |
| `test` | 测试相关 |
| `docs` | 文档 |
| `chore` | 构建/工具/配置 |
---
## §4. 问题管理
### 4.1 问题来源
| 来源 | 入口 | 处理方式 |
|------|------|---------|
| 主公口头 | 庞统记录,**经 Review 后** | 创建 Gitea Issue |
| 司马懿 Review | Review 意见 | 创建 Gitea Issue,关联 PR |
| 自动化测试失败 | CI 报告 | 自动评论到已有 Issue,或手动创建 |
| 用户测试反馈 | 人工报告 | 创建 Gitea Issue |
| Agent 运行发现 | Agent 日志,**经 Review 后** | 创建 Gitea Issue |
### 4.2 Issue 标签体系
| 标签 | 颜色 | 说明 |
|------|------|------|
| `bug` | 红 | 功能异常 |
| `feature` | 蓝 | 新功能需求 |
| `improvement` | 绿 | 改进优化 |
| `security` | 橙 | 安全相关 |
| `risk:high/standard/low` | 分级色 | 风险级别(见 §6.1 判定规则) |
| `priority:high/medium/low` | 黄/灰 | 优先级 |
| `blocked` | 紫 | 阻塞中 |
### 4.3 需求/问题 Review 前置
所有需求和问题在进入开发之前必须经过 Review:
| 类型 | Review 者 | 目的 |
|------|-----------|------|
| 新需求 | 庞统(方案合理性)+ 司马懿(技术可行性) | 确认需求清晰、可行、值得做 |
| Bug/问题 | 司马懿(确认根因)或庞统(确认优先级) | 确认是真 bug、不是误报,根因明确 |
| Agent 发现的问题 | 庞统(确认有效性) | 过滤误报 |
未经过 Review 的 Issue 不进入开发流程。
### 4.4 Issue 状态流转
```
Open → In Progress → Review → Closed
↑ ↓
└──── Reopened ←───────────────────┘
```
---
## §5. CI/CD 管道设计
### 5.1 通用 CI 流程(所有项目共享骨架)
每个项目在 `.gitea/workflows/ci.yml` 自定义具体步骤,但遵循统一骨架。
> **注**Gitea Actions v1.23.4 不支持 `paths` 过滤触发条件。通过路径判断放在 job 级别的 `if` 条件中,使用确定支持的语法。(M4 修订)
```yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
# Job 1: 后端测试
backend:
# 只在有 Python 代码变更时跑
runs-on: [self-hosted, mac-mini]
steps:
- uses: actions/checkout@v4
- name: Setup Python
run: |
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
- name: Lint
run: .venv/bin/ruff check src/
- name: Unit Tests
run: .venv/bin/pytest tests/unit/ -m "not e2e" --tb=short
- name: Integration Tests
run: .venv/bin/pytest tests/integration/ --tb=short
- name: Coverage Report
run: .venv/bin/pytest --cov=src --cov-report=term-missing
# Job 2: 前端构建
frontend:
# 只在有前端目录时跑
runs-on: [self-hosted, mac-mini]
steps:
- uses: actions/checkout@v4
- name: Install & Build
run: |
cd src/frontend
npm ci
npm run build
- name: Type Check
run: cd src/frontend && npx tsc --noEmit
```
**实际使用时**:每个项目根据实际情况删减 job。没有前端的删 frontend job,没有 Python 的删 backend job。
### 5.2 CI 门控级别
| 级别 | 触发条件 | 跑什么 | 阻断行为 |
|------|---------|--------|---------|
| **快速** | feature 分支 push | lint + unit tests | **软阻断**:失败标红,创建 PR 时如果最近一次 CI 失败则不允许进入 Review(S2 修订) |
| **标准** | PR 到 main | lint + unit + integration + coverage | 阻断 merge |
| **完整** | tag / main push | 标准 + buildE2E 仅 tag 触发) | 阻断部署 |
| **手动** | 手动触发 | 完整 + 特定检查 | 按需 |
### 5.3 覆盖率渐进策略(仲达 Q3 补充)
当前多数项目从未跑过覆盖率,直接设阈值不现实。分三阶段:
| 阶段 | 时间 | 覆盖率策略 | 说明 |
|------|------|-----------|------|
| **P1** | 启用后 2 周 | 只报告不阻断 | 先拿基线数据 |
| **P2** | 启用后 1 月 | 40% 阈值,低于警告不阻断 | 逐步提升 |
| **P3** | 启用后 2 月+ | 60% 阈值,低于阻断 | 正式生效 |
### 5.4 CI 结果反馈
- ✅ 通过 → PR 可合并
- ❌ 失败 → PR 阻断,自动评论失败信息
- 覆盖率低于阈值 → P1 只报告,P2 警告,P3 阻断
---
## §6. 代码审查流程
### 6.1 风险级别判定规则(M2 修订)
风险级别不由改动者自评,按以下规则自动判定 + 非改动者确认:
**自动判定规则**(在 CI 或 PR 模板中检查):
| 规则 | 级别 | 触发条件 |
|------|------|---------|
| 涉及核心模块 | **高** | 改动文件匹配 `**/spawner*``**/ticker*``**/dispatcher*``**/router*``**/guardrails*``**/strategy*``**/risk*` |
| 涉及资金/安全 | **高** | 改动包含交易逻辑、风控规则、密钥管理、资金操作 |
| 数据库 schema 变更 | **高** | 改动匹配 `**/migrations/**``**/schema*``**/models.py` 中的表定义 |
| 常规功能开发 | **标准** | 不匹配高风险规则的 src/ 下改动 |
| 文档/测试/配置 | **低** | 改动仅在 `docs/``tests/``*.md``*.yaml`(非 guardrails |
**确认机制**
1. PR 创建时自动标注风险级别(基于 git diff 文件路径)
2. 审查者(或庞统)确认或升级——**只能升级不能降级**
3. 改动者可以申请升级(但不能申请降级)
### 6.2 审查方式(按风险分级)
| 风险级别 | 审查方式 | 审查者 |
|---------|---------|--------|
| **高** | 固定 Review(必须) | 司马懿(专职) |
| **标准** | 固定 Review(必须) | 司马懿(默认审查者) |
| **低** | 简化走查 | 司马懿(快速确认)+ CI 通过 |
**例外**:司马懿自己的改动 → 庞统交叉审查。
> 所有级别的代码审查默认由司马懿执行。只有司马懿作为改动者时,才由庞统进行交叉审查。不再按改动领域分配审查者——简化流程,减少协调成本。
### 6.3 审查者分配规则
| 改动者 | 审查者 |
|--------|--------|
| 张飞 / 姜维 / 关羽 / 赵云 / 庞统 | 司马懿(默认) |
| 司马懿 | 庞统(交叉审查) |
### 6.4 审查清单(Skill 固化)
审查清单通过 Skill 提示词固化,不依赖审查者自觉。**基础清单 + 项目类型变体**:
**基础清单(所有项目通用)**
```
1. 正确性
- [ ] 代码逻辑正确,无死循环/空指针/边界遗漏
- [ ] 异常处理完整(网络超时、文件不存在、参数非法)
- [ ] 改动范围和 Issue/需求描述一致(不多不少)
2. 一致性
- [ ] 命名规范统一(变量/函数/文件)
- [ ] 和已有代码风格一致
3. 安全性
- [ ] 无硬编码密钥/密码/Token
- [ ] 用户输入有校验
- [ ] 文件操作有路径校验
4. 可维护性
- [ ] 复杂逻辑有注释(为什么,不是做什么)
- [ ] 无重复代码(DRY)
- [ ] 关键参数可配置,无魔法数字
5. 测试覆盖
- [ ] 新功能有对应测试
- [ ] Bug 修复有回归测试
- [ ] CI 通过
结论:通过 / 不通过(附具体行号和修改方向)
```
**量化策略项目变体**(追加项):
```
6. 量化策略专项
- [ ] 止损逻辑完整(触发条件、执行路径、异常兜底)
- [ ] 仓位计算正确(滑点、手续费已计入)
- [ ] 回测参数和实盘参数分离
```
### 6.5 审查时效
| 改动大小 | 期望审查时间 |
|---------|-------------|
| < 100 行 | 2 小时内 |
| 100-500 行 | 半天内 |
| > 500 行 | 1 天内 |
---
## §7. 部署流程
### 7.1 部署触发
| 触发方式 | 适用场景 | 流程 |
|---------|---------|------|
| **自动**(tag 触发) | 正式发布 | 打 tag → CI 完整跑(含 E2E)→ 自动执行 deploy 脚本 → 健康检查 |
| **自动**main push | 日常迭代 | push main → CI 标准跑 → 自动执行 deploy 脚本 → 健康检查 |
| **手动** | 紧急/调试 | 手动执行项目 deploy 脚本 |
### 7.2 项目部署脚本规范
每个项目必须提供 `scripts/deploy.sh`,接口统一:
```bash
#!/usr/bin/env bash
# deploy.sh — 项目部署脚本
#
# 必须支持的参数:
# --source=DIR 源码目录(CI 自动传入:源码 checkout 路径)
# --target=DIR 安装目标目录(CI 自动传入:安装路径)
# --skip-build 跳过构建步骤
# --health-check 部署后执行健康检查
# --version 输出当前部署版本(tag + commit hash + 部署时间)
# --rollback 回滚到上一个已记录的部署版本
#
# 必须维护的文件:
# data/deploy-history.jsonl — 最近 10 个部署版本记录
# 每行: {"tag":"v1.2.3","commit":"abc123","timestamp":"2026-06-06T12:00:00+08:00","status":"success"}
#
# 必须返回:
# 0 = 成功
# 非0 = 失败(stderr 输出原因)
```
### 7.3 自动部署 Workflow
```yaml
# .gitea/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
tags: ['v*']
jobs:
deploy:
needs: [ci] # 依赖 CI 通过
runs-on: [self-hosted, mac-mini]
steps:
- uses: actions/checkout@v4
- name: Record current version
run: bash scripts/deploy.sh --version
- name: Deploy
run: bash scripts/deploy.sh --source="$GITHUB_WORKSPACE" --health-check
- name: Rollback on failure
if: failure()
run: bash scripts/deploy.sh --rollback
```
### 7.4 健康检查
每个项目的 deploy 脚本必须包含健康检查:
- HTTP 服务:`curl -sf http://localhost:<port>/api/health`
- 数据服务:数据连通性检查
- 定时任务:PM2 状态检查
### 7.5 回滚策略(M3 修订)
**版本记录**
- 每次 deploy 成功后,记录 tag + commit + 时间戳到 `data/deploy-history.jsonl`(保留最近 10 条)
- `--version` 读取最后一行输出当前版本
- `--rollback` 读取倒数第二行,checkout 对应 commit,重新 deploy
**回滚流程**
1. `deploy.sh --rollback` 读取上一个成功版本
2. `git checkout <previous-commit>`
3. 重新执行 deploy(不含 rollback
4. 健康检查
**已知限制**
- revert 可能产生合并冲突 → 部署失败时人工介入
- 数据库变更回滚需人工介入 → schema 变更必须向前兼容(只加字段不删/不改),违反此规范由 CI 检查拦截(或人工 Review 拦截)
---
## §8. 验证流程集成
### 8.1 验证来源和层级
| 验证来源 | 层级 | 触发时机 | 自动化程度 |
|---------|------|---------|-----------|
| **Lint** | 机械 | 每次 push | 100% 自动 |
| **单元测试** | 机械 | 每次 push | 100% 自动 |
| **集成测试** | 语义 | PR 到 main | 100% 自动 |
| **覆盖率** | 语义 | PR 到 main | 渐进自动(见 §5.3 |
| **Code Review** | 共识 | PR 到 main | 人工(Skill 辅助) |
| **E2E 测试** | 共识 | PR 到 main 时自动跑(CI 隔离环境)+ 手动触发 | 自动+手动 |
| **用户测试** | 共识 | 部署后 | 手动 |
### 8.2 三阶段门控(来源: quality-gate-patterns
```
机械门控 (CI 快速) → 语义门控 (CI 标准 + Review) → 共识门控 (E2E + 用户验证)
零成本 中等成本 高成本
语法/lint/编译 API 契约/业务逻辑 端到端/用户体验
```
- 快速门控是**开发期辅助反馈**,不替代标准门控
- **PR 合入必须经过标准门控(机械 + 语义)**,这个顺序不可跳过(S3 修订)
- 机械失败 → 立即阻断,不看 Review
- 语义失败 → 可以 Review 但不能合并
- E2E 在 CI 隔离环境中自动跑,不污染生产环境(见 §8.5)。也可手动触发。
### 8.3 测试失败 → Issue 关联
- CI 失败时,如果已有 Issue → 自动评论失败信息
- CI 失败时,如果无 Issue → 不自动创建(避免 Issue 爆炸),由改动者手动创建
### 8.4 机械门控前提条件(仲达 Q6 补充)
P1 必须先配好以下工具,否则机械门控跑不起来:
| 工具 | 用途 | 安装 |
|------|------|------|
| `ruff` | Python lint + format | `pip install ruff` |
| `pytest-cov` | 覆盖率 | `pip install pytest-cov` |
| `act-runner` | CI 执行器 | 下载 Go 二进制 |
### 8.5 CI 隔离测试环境(E2E 安全运行)
E2E 自动跑的核心前提:**不污染生产环境**。通过 CI 隔离环境实现:
```
CI 临时测试环境(每次 CI 自动创建)
├─ 独立 venv(每次 CI 新建)
├─ 临时 SQLitetmpdir,跑完销毁)
├─ 临时端口(8084,不和生产 8083 冲突)
├─ 启动 FastAPI 服务 → 跑 E2E → 关闭
└─ 跑完整个 tmpdir 删除,不留痕迹
生产环境(~/.sanguo_projects/)完全不受影响
```
**效果**E2E 可以在每次 PR 到 main 时自动跑,无需担心污染生产数据或影响生产服务。
#### 隔离边界:程序逻辑 vs Agent 交互
| 层面 | 隔离方式 | 说明 |
|------|---------|------|
| **程序逻辑**FastAPI + Daemon + SQLite | ✅ 完全隔离 | 独立 venv + 临时数据库 + 临时端口 | — |
| **Agent spawn**openclaw agent | ❌ 走生产 openclaw | openclaw 是全局单例,无法隔离 | 测试 case 用 UUID 前缀标识 + Agent 回写地址指向 8084 临时端口 |
Agent spawn 走生产 openclaw 的决策理由:
1. openclaw 是全局单例(`~/.openclaw/` 只有一份),Agent 配置和 workspace 全局共享
2. 测试 case 会标识测试上下文,Agent 记忆混淆风险低
3. 完全隔离需要在 Docker 中跑独立 openclaw,维护成本不值得
4. Agent 的 API 回写地址由 spawn message 传入,指向 CI 临时服务的端口 8084
#### 路径硬编码修复清单
当前代码有 6 处硬编码绝对路径,需改为环境变量可配置(P1 必须完成):
| 位置 | 硬编码路径 | 改法 | 现状 | 行数 |
|------|-----------|------|------|------|
| `utils.py` | 数据根目录 | `BLACKBOARD_ROOT` 环境变量 | ✅ 已支持 | 1 |
| `bootstrap.py:42` | Skill 加载路径 | `MOZI_SKILL_PATH` 环境变量 | ✅ 已支持 | 1 |
| `registry.py:264` | 项目扫描目录 `~/.openclaw/sanguo_projects` | 加 `SANGUO_PROJECTS_DIR` | ❌ 硬编码 | 1 |
| `spawner.py:1177,1261` | `~/.openclaw/agents/<id>/sessions.json` | 加 `OPENCLAW_HOME` | ❌ 硬编码 | 2 |
| `blackboard_routes.py:161` | `~/.openclaw/openclaw.json` | 从 `OPENCLAW_HOME` 读 | ❌ 硬编码 | 1 |
共计 6 处(2 已支持、4 待修)。
#### 环境总结
| 环境 | 位置 | 用途 | 生命周期 |
|------|------|------|---------|
| 开发 | `~/.openclaw/sanguo_projects/` | 改代码、本地调试 | 常驻 |
| CI 测试 | CI runner 临时目录 | lint/unit/integration/E2E | CI 跑完即销毁 |
| 生产 | `~/.sanguo_projects/` | 正式运行服务 | 常驻 |
**schema 变更检查**(可选):CI 中加一步对比数据库 schema 变更,检测破坏性操作(删字段/改类型)。检测到则提 Issue 人工确认。可通过配置开关强制绕过。
---
## §9. 完整开发流程(端到端)
### 9.0 全局工作流串联图
以下是把所有关键环节串起来的完整链路。三个场景(新功能/Bug修复/Hotfix)共享大部分节点,差异在分支。
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 问题/需求入口 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 主公口头 │ │ CI 失败 │ │ 司马懿 │ │ 运行时 │ │
│ │ /需求文档 │ │ 自动报告 │ │ Review │ │ Agent发现 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └──────────────┴──────────────┴──────────────┘ │
│ ↓ │
│ ┌─────────────────────┐ │
│ │ 需求/问题 Review │ ← §4.3 │
│ │ │ │
│ │ 需求: 庞统(方案) │ │
│ │ + 司马懿(可行) │ │
│ │ Bug: 司马懿(确认根因)│ │
│ │ Agent发现: 庞统(过滤)│ │
│ └──────────┬──────────┘ │
│ ↓ 通过 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 创建 Gitea Issue #N │ │
│ │ 标签: bug/feature/improvement + risk:high/standard/low │ │
│ │ 关联: PR / 复现步骤 / 根因分析 │ │
│ └──────────────────────────┬────────────────────────────────┘ │
│ ↓ │
│ ┌──── 场景分流 ────┐ │
│ ↓ ↓ │
│ ┌─── Hotfix ───┐ ┌── 标准流程 ──┐ │
│ │ │ │ │ │
│ │ 直接 main │ │ 分支开发 │ │
│ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ ↓ ↓ │
└─────────────────────────────────────────────────────────────────────────┘
=== 标准流程(新功能 / Bug修复)===
┌─────────────────────────────────────────────┐
│ Step 1: 分支创建 │
│ git checkout -b feat/issue-N-xxx │
│ 或 git checkout -b fix/issue-N-xxx │
│ 角色Agent: 开发者(张飞/姜维/关羽/赵云) │
│ Skill: git-workflow │
│ 输入: Issue #N │
│ 输出: feature/bugfix 分支 │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step 2: 编码 + 自测 │
│ 编码 → 本地 pytest → commit │
│ 角色Agent: 开发者 │
│ Skill: 按任务类型(无专门编码skill) │
│ 输入: 分支 + Issue 需求 │
│ 输出: 代码改动 + UT │
│ 约束: Bug修复必须加回归测试 │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step 3: push → CI 快速门控(每次push) │
│ Lint + Unit Tests │
│ 工具: Gitea Actions │
│ 输入: feature 分支 push │
│ 输出: ✅通过 / ❌失败标红 │
│ 阻断: 软阻断(失败不阻止开发,但阻止PR进入Review)│
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step 4: 创建 PR │
│ git push origin → Gitea 创建 PR │
│ 自动: 风险级别标注(按改动文件路径) │
│ 工具: Gitea PR │
│ 输入: feature 分支 + CI 快速门控结果 │
│ 输出: PR + 风险标签 │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step 5: CI 标准门控(PR到main时) │
│ Lint + Unit + Integration + Coverage │
│ 环境: CI 隔离临时环境 │
│ 工具: Gitea Actions + pytest-cov + ruff │
│ 输入: PR diff │
│ 输出: ✅通过 / ❌阻断merge │
│ 阻断: 硬阻断(CI不过不许merge) │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step 6: 代码审查 │
│ 审查者确认风险级别(只升不降) │
│ 默认审查者: 司马懿(所有级别) │
│ 例外: 司马懿的改动 → 庞统交叉审查 │
│ Skill: code-review │
│ 输入: PR + CI 结果 + 风险级别 │
│ 输出: 通过 / 不通过(附行号+修改方向) │
│ 不通过 → 回到 Step 2 │
└──────────────────┬──────────────────────────┘
↓ 通过
┌─────────────────────────────────────────────┐
│ Step 7: Merge → CI 完整门控 │
│ merge到main → 自动触发 │
│ 标准 CI + deploy workflow │
│ 触发: Gitea branch protectionCI+Review通过才许merge)│
│ 输入: main 分支 │
│ 输出: merge commit + CI 结果 │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step 8: 自动部署 │
│ deploy.sh --source=... --health-check │
│ 记录版本到 deploy-history.jsonl │
│ 角色Agent: 姜维(deploy.sh由项目维护) │
│ Skill: ci-cd-ops │
│ 输入: main 分支代码 │
│ 输出: 部署结果 + 版本记录 │
│ 失败: 自动回滚(deploy.sh --rollback
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step 9: E2E 验证(CI 隔离环境自动跑) │
│ 临时venv + 临时SQLite + 临时端口8084 │
│ Agent spawn 走生产 openclaw(不隔离) │
│ 角色: 司马懿写E2E用例,CI自动跑 │
│ Skill: testing-workflow │
│ 输入: 部署后的代码 │
│ 输出: E2E 结果 │
│ 阻断: 初期不阻断,成熟后可阻断 │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step 10: Issue 关闭 │
│ 部署成功 + 验证通过 → 关闭 Issue #N │
│ 发现新问题 → 新开 Issue → 回到入口 Review │
└─────────────────────────────────────────────┘
=== Hotfix 分支流程 ===
┌─────────────────────────────────────────────┐
│ Step H1: 确认 hotfix │
│ 庞统或主公确认(线上故障、核心功能受影响) │
│ Skill: hotfix-workflow │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step H2: 直接 push main │
│ 修改 → commit → push main │
│ CI 必须触发(lint + unit),不可跳过 │
│ CI 失败 → 自动 revert → 人工介入 │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step H3: 自动部署(同标准 Step 8) │
│ E2E 验证(同标准 Step 9) │
└──────────────────┬──────────────────────────┘
┌─────────────────────────────────────────────┐
│ Step H4: 24h 内复盘(必须) │
│ 补 Gitea Issue(根因 + hotfix 内容) │
│ 司马懿 Review(事后审查代码质量) │
│ 复盘记录(为什么需要 hotfix、如何避免) │
│ 超时 → 下次 hotfix 权限暂停 │
└─────────────────────────────────────────────┘
```
#### 衔接机制:每一步怎么触发下一步
上图只画了"谁做什么",这里补清楚**步骤之间靠什么机制衔接**。
| 衔接点 | 触发方式 | 通知方式 | 技术实现 |
|--------|---------|---------|----------|
| 问题入口 → 问题Review | 庞统收到问题后主动发起 | Mail 通知司马懿(需求类)/ 司马懿自己提交(bug类) | Mail API POST `/api/mail` |
| 问题Review → Issue创建 | Review结论达成 | 庞统或司马懿创建 | Gitea API `POST /repos/{owner}/{repo}/issues` |
| Issue创建 → 分支创建 | 庞统指派任务给开发者 | Mail 通知被指派者,含 Issue #N + 分支名 | Mail API + Agent 读取 Issue |
| 分支创建 → 编码 | 开发者看到 Mail 后开始 | 无额外通知 | Agent 启动时 Skill 注入分支信息 |
| 编码 → CI 快速门控 | **git push 自动触发** | CI 结果显示在 commit 状态(Gitea Status Check 内置) | Gitea Actions `on: push` |
| CI 快速门控 → PR创建 | 开发者主动(认为代码写完) | 无自动通知 | 开发者在 Gitea Web UI 或 git push + 浏览器创建 PR |
| PR创建 → CI 标准门控 | **PR 创建/更新自动触发** | CI 结果自动评论到 PR | Gitea Actions `on: pull_request` |
| CI 标准门控 → 代码审查 | **CI 通过后 daemon Webhook 转发 Mail 给审查者** | Mail 通知司马懿 Review | Gitea Webhook `pull_request` → daemon Webhook 模块 → Mail API |
| 代码审查 → 修改(不通过) | 审查者提交 Review 意见 | daemon Webhook 转发 Mail 通知改动者(附 Review 摘要) | Gitea Webhook `pull_request_review` → daemon Webhook 模块 → Mail API |
| 代码审查 → Merge(通过) | **审查者点 Approve** | daemon Webhook 转发 Mail 通知改动者 merge | Gitea Webhook `pull_request_review` → daemon Webhook 模块 → Mail API |
| Merge → 部署 | **merge 到 main 自动触发** | 无需通知(自动化) | Gitea Actions `on: push: branches: [main]` |
| 部署 → E2E | **部署 job 成功后触发 E2E job** | E2E 结果评论到 merge commit | Gitea Actions `needs: [deploy]` |
| E2E/部署 → Issue关闭 | 庞统或改动者手动确认后关闭 | Issue 关闭通知关注者 | Gitea API `PATCH /repos/{owner}/{repo}/issues/{id}` state=closed |
| CI失败 → Issue评论 | **CI 失败自动评论** → daemon Webhook 转发 Mail 通知改动者 | 评论到关联 Issue + Mail 推送 Agent | Gitea Actions `if: failure()` 写 PR评论 → daemon Webhook 监听 `issue_comment` → Mail |
| 部署失败 → 自动回滚 | **deploy.sh 失败自动执行** | 无需通知(自动化) | Gitea Actions `if: failure()` step 调 `deploy.sh --rollback` |
#### 关键衔接机制详解
**1. Gitea Actions 自动触发(核心衔接)**
整个 CI/CD 链路的自动化靠 Gitea Actions 的触发机制:
```
git push feature分支
→ Gitea Actions on: push 匹配
→ 跑 lint + unit(快速门控)
创建 PR
→ Gitea Actions on: pull_request 匹配
→ 跑 lint + unit + integration + coverage(标准门控)
→ CI 结果自动出现在 PR 页面
Merge 到 main
→ Gitea Actions on: push: branches: [main] 匹配
→ 跑 deploy workflow(含 deploy.sh + health check + E2E
→ 全自动,无需人干预
```
触发链:push → CI → PR → CI → Review → merge → deploy+E2E,中间三个自动环节靠 Gitea Actions 的 `on` 触发器串联。
**2. Mail 通知(Agent 间协调)**
CI/CD 之外的衔接靠 Mail。Agent 不会自己去看 Gitea PR 评论,所以所有需要 Agent 行动的情况都通过 Mail 推送:
- 需求Review完成 → Mail 通知开发者开始
- Bug确认 → Mail 通知改动者(含根因)
- Hotfix确认 → Mail 通知执行者
- CI 失败 → CI workflow 写 PR 评论 → daemon Webhook 模块转发 Mail 通知改动者 Agent
- 审查不通过 → Mail 通知改动者(附 Review 摘要)
- 部署失败回滚 → Mail 通知庞统/姜维(人工介入)
**3. Gitea branch protection(硬门控)**
确保流程不可跳过:
- `main` 分支设置:CI 必须通过 + 至少 1 人 Review Approve
- 效果:没有 CI 绿勾 + Review Approvemerge 按钮灰色不可点
- 配置位置:Gitea → 仓库 Settings → Branches → Branch Protection
**4. Agent 间通知机制总结**
| 场景 | 通知方式 | 为什么不用另一种 |
|------|---------|----------------|
| 人→Agent任务指派 | Mail | Gitea Issue 不会主动通知 Agent,需要 Mail 推送 |
| CI 结果 | Gitea Status CheckPR页面)+ daemon Webhook 转发 MailAgent通知) | Status Check 给人看,Mail 给 Agent 推送 |
| Review 请求 | daemon Webhook 转发 Mail | Gitea Webhook 触发 → daemon 翻译成 Mail 给司马懿 |
| Review 意见(不通过/通过) | daemon Webhook 转发 Mail | Gitea Webhook 触发 → daemon 翻译成 Mail 给改动者 |
| CI 失败 | CI workflow 写 PR评论 → daemon Webhook 转发 Mail | CI 写评论是给所有人看,Webhook → Mail 给 Agent 推送 |
| 跨Agent协作请求 | Mail | Gitea 不支持跨 Agent 私信 |
### 9.0.1 通知模板(工具链通用版)
以下为 §9.0 衔接机制中用到的通用通知模板。§15.5 中还定义了更详细的流程强约束 Mail 模板(含完整流程步骤和 Gitea API 调用指令)。
所有自动化通知统一格式,确保 Agent 收到后能直接行动。
**模板 1:任务指派(庞统 → 开发者)**
```
From: pangtong-fujunshi
To: {被指派者}
Title: 任务指派: Issue #{N} - {Issue 标题}
Text:
{Issue 标题}
Issue: http://192.168.2.154:3000/{repo}/issues/{N}
分支: feat/issue-{N}-{简述}
风险级别: {high/standard/low}
要点:
- {需求要点1}
- {需求要点2}
验收标准:
- {验收标准1}
- {验收标准2}
```
**模板 2CI 失败通知(CI workflow → 改动者)**
```
From: daemon-webhook
To: {PR 作者}
Title: CI 失败: PR #{N} - {PR 标题}
Text:
PR #{N} CI 未通过。
PR: http://192.168.2.154:3000/{repo}/pulls/{N}
失败测试:
- {test_path}::{test_name} FAILED
- {test_path}::{test_name} FAILED
失败 step: {step_name}
错误摘要: {error_output_tail_20_lines}
请修复后重新 push。
```
**模板 3:Review 不通过通知(审查者 → 改动者)**
```
From: {审查者}
To: {改动者}
Title: Review 不通过: PR #{N} - {PR 标题}
Text:
PR #{N} 审查未通过。
PR: http://192.168.2.154:3000/{repo}/pulls/{N}
风险级别: {confirmed_level}
审查意见:
1. [{文件}:{行号}] {问题描述} → {修改方向}
2. [{文件}:{行号}] {问题描述} → {修改方向}
必须全部修改后重新 push。
```
**模板 4:部署失败回滚通知(CI workflow → 庞统/姜维)**
```
From: daemon-webhook
To: pangtong-fujunshi, jiangwei-infra
Title: 部署失败已回滚: {repo} {commit_hash}
Text:
{repo} 部署失败,已自动回滚到上一版本。
触发 commit: {commit_hash}
部署脚本: scripts/deploy.sh
失败原因: {deploy_stderr_tail}
当前版本: {previous_version}
请人工介入排查。
```
**模板 5:Bug 确认通知(司马懿 → 改动者)**
```
From: simayi-challenger
To: {改动者}
Title: Bug 确认: Issue #{N} - {Issue 标题}
Text:
根因: {根因描述}
复现步骤: {steps}
影响范围: {scope}
风险级别: {level}
Issue: http://192.168.2.154:3000/{repo}/issues/{N}
分支: fix/issue-{N}-{简述}
请修复并添加回归测试。
```
### 9.1 场景 A:新功能开发
```
1. 问题入口
└─ 主公口头 / 需求文档
└─ 庞统梳理需求方案 → 司马懿确认技术可行性
└─ Review 通过后 → 庞统创建 Gitea Issue #N
2. 分支创建
└─ git checkout -b feat/issue-N-xxx
└─ Agent 或人工编码(git-workflow Skill 注入分支信息到 Agent 上下文)
3. 开发过程中
└─ 每个 commit → push → CI 快速门控(lint + unit
└─ CI 失败 → 标红但不阻断开发 → 修好再 push
4. 开发完成
└─ git push origin feat/issue-N-xxx
└─ 创建 PR → 自动标注风险级别(基于改动文件路径)
└─ CI 标准门控(lint + unit + integration + coverage
5. 审查
└─ CI 通过后进入审查
└─ 审查者确认风险级别(只升不降)
└─ 司马懿审查(所有级别默认)
└─ 如果改动者是司马懿 → 庞统交叉审查
└─ 审查不通过 → 修改 → push → CI → 再审
└─ 审查通过 → merge
6. 部署
└─ merge 到 main → 自动触发 CI + deploy workflow
└─ deploy.sh 执行 → 记录版本 → 健康检查
└─ 部署成功 → Issue #N 关闭
└─ 部署失败 → 自动回滚 → 人工介入
7. 验证
└─ CI 隔离环境中自动跑 E2E(不污染生产)
└─ 用户测试(手动)
└─ 发现问题 → 新开 Issue(经 Review 后再开发)
```
### 9.2 场景 BBug 修复
```
1. 问题入口
└─ CI 失败 / 司马懿 Review / 运行时发现
└─ 司马懿确认根因(是真 bug 不是误报)
└─ 创建 Gitea Issue #N,标注 bug + 风险级别
2. 根因定位(bugfix-workflow Skill
└─ 调查根因(不猜,看日志/复现)
└─ 在 Issue 中记录根因
3. 修复
└─ git checkout -b fix/issue-N-xxx
└─ 修改代码 + 添加回归测试(必须)
└─ push → PR → CI → Review
4. 合并+部署
└─ merge → auto deploy → 健康检查
└─ Issue #N 关闭
5. 验证
└─ 确认原 bug 不再复现
```
### 9.3 场景 C:紧急修复(hotfix)(M1/S5 修订)
```
1. 判断是否需要 hotfix
└─ 标准:线上故障影响核心功能,等 PR 流程代价过高
└─ 庞统或主公确认
2. 直接在 main 上修
└─ git commit → push main
└─ CI 必须触发(lint + unit),不可跳过
└─ auto deploy → 健康检查
└─ CI 失败 → 自动 revert → 人工介入
3. 事后复盘(hotfix-workflow Skill
└─ 24 小时内必须完成:
├─ 补 Gitea Issue(记录根因 + hotfix 内容)
├─ 司马懿 Review(事后审查代码质量)
└─ 复盘记录(为什么需要 hotfix、如何避免)
└─ 超时未复盘 → 下次 hotfix 权限暂停
4. E2E 验证
└─ CI 隔离环境中自动跑 E2E 确认 hotfix 有效
```
---
## §10. Skill 清单(流程固化)(S4 修订)
以下流程通过 Skill 固化到对应 Agent
| Skill 名 | 固化什么 | 适用 Agent |
|----------|---------|-----------|
| `git-workflow` | 分支创建/命名/合并规范、commit 规范、PR 创建流程、**当前分支注入上下文** | 所有编码 Agent |
| `code-review` | 风险级别判定规则、审查清单(基础+项目变体)、审查标准、结论格式 | 司马懿(默认审查者)、庞统(交叉审查) |
| `ci-cd-ops` | CI 配置规范、deploy 脚本规范、健康检查标准、版本记录规范 | 姜维(平台)、所有项目维护者 |
| `bugfix-workflow` | 根因定位流程、回归测试要求、Issue 关联 | 所有编码 Agent |
| `hotfix-workflow` | hotfix 判断标准、直接 push main 流程、CI 必跑、24h 复盘要求 | 庞统(协调)、张飞、姜维 |
| `testing-workflow` | 测试分工规范、什么时候跑什么测试、测试数据隔离 | 所有 Agent |
| `release-workflow` | tag 规范、changelog、发布流程、数据库 schema 变更向前兼容规范 | 庞统(协调) |
### 测试分工规范(testing-workflow Skill 内)
| 测试类型 | 谁写 | 谁跑 |
|---------|------|------|
| **单元测试(UT** | 开发者自己(张飞/姜维/关羽/赵云等) | CI 自动 + 开发者本地 |
| **功能测试** | 司马懿(专职) | CI 自动 |
| **集成测试** | 司马懿(专职) | CI 自动 |
| **E2E 测试** | 司马懿(专职) | CI 自动(隔离环境)+ 手动触发 |
| **测试脚本/工具** | 司马懿 + 赵云(数据相关) | 按需 |
> 所有人都能写代码和写 UT。但功能测试、集成测试、E2E 由司马懿专职编写和维护——确保测试视角独立于开发视角。
---
## §11. 实施路线
| 阶段 | 内容 | 前置条件 | 耗时 |
|------|------|---------|------|
| **P0: 基础启用** | Gitea admin 开启 Actions + 部署 act-runnerMac mini 裸机) | 主公操作 NAS | 30 分钟 |
| **P1: CI 骨架** | 模板 CI workflow + pytest-cov + ruff + 覆盖率基线测量 | P0 | 1 天 |
| **P2: 流程 Skill** | 7 个 Skill 编写(见 §10 | 无 | 2-3 天 |
| **P3: 自动部署** | 部署 workflow + 项目 deploy.sh 规范化 + 版本记录 | P1 | 1-2 天 |
| **P4: 渐进收紧** | 覆盖率阈值 P1→P2→P3、E2E 集成 | P1-P3 | 按需 |
**P1 必须先就位的工具**(否则机械门控跑不起来):
- `ruff`lint
- `pytest-cov`(覆盖率)
- `act-runner`CI 执行器)
---
## §12. 评审记录
### v0.1 → v0.2 修订清单
| 编号 | 类型 | 问题 | 修订内容 |
|------|------|------|---------|
| M1 | 必须修 | hotfix 安全网不足 | §3.3 明确 hotfix push main 后 CI 必跑,失败自动 revert;§9.3 统一流程 |
| M2 | 必须修 | 风险级别谁来定 | §6.1 新增自动判定规则(按文件路径)+ 非改动者确认机制(只升不降) |
| M3 | 必须修 | 回滚策略过于简单 | §7.2 新增 `--version` 参数和 deploy-history.jsonl;§7.5 重写回滚策略 |
| M4 | 必须修 | CI 语法问题 | §5.1 删除 `hasPython()`/`hasFrontend()` 伪函数,改为每个项目按需删减 job |
| S1 | 建议改 | 轮查配对不合理 | §6.3 改为按改动领域分配,不再固定轮转 |
| S2 | 建议改 | 快速门控"不阻断"是噪音 | §5.2 改为"软阻断"PR 进入 Review 前检查最近 CI 状态 |
| S3 | 建议改 | 三阶段门控说明不清 | §8.2 补充:快速门控是开发辅助,PR 合入必须标准门控 |
| S4 | 建议改 | Skill 遗漏 | §10 增加 testing-workflow、hotfix-workflow、release-workflow(含 schema 变更) |
| S5 | 建议改 | hotfix 复盘无时限 | §9.3 加 24 小时复盘约束,超时暂停 hotfix 权限 |
| Q1 | 评审回答 | Trunk-Based 够用吗 | §3.1 补充前提:Agent 必须知道当前分支,git-workflow Skill 注入 |
| Q3 | 评审回答 | 覆盖率基线缺失 | §5.3 新增渐进策略(P1 只报告 → P2 40% → P3 60% |
| Q6 | 评审回答 | 机械门控前提未就绪 | §8.4 新增工具清单,§11 标注 P1 必须先就位 |
### v0.3 → v0.4 修订清单(主公审阅反馈)
| 编号 | 反馈 | 修订内容 |
|------|------|---------|
| F1 | 需求/问题必须经 Review | §1 新增原则 7;§4.3 新增需求 Review 前置规则;§9.1 场景 A 加入 Review 步骤 |
| F2 | E2E 仅用户触发 | §8.1 E2E 改为“仅用户手动触发”;§8.2 门控说明移除 E2E 自动跑;§9.1/9.3 验证步骤更新 |
| F3 | 编码/测试人人能做,功能/集成/E2E 司马懿专职 | §10 新增测试分工规范表 |
| F4 | 审查默认司马懿 | §6.2 简化为司马懿统一审查,仅司马懿自身改动由庞统交叉审查 |
| F5 | 以项目为单位组织 | §1 新增原则 8;Issue 标签体系含项目维度 |
| F6 | 姜维持有 Gitea admin | §2.1 权限更新 |
| F7 | 分支保护含义说明 | §3.3 补充分支保护规则说明 |
| F8 | schema 变更能自动化就自动化 | 保留在 release-workflowCI check + Issue 兜底 |
### v0.4 → v0.5 修订清单(主公二次审阅)
| 编号 | 反馈 | 修订内容 |
|------|------|----------|
| G1 | E2E 不自动跑是因为怕污染生产,不是不需要 | §8.5 新增 CI 隔离测试环境,E2E 改为自动跑(临时 venv + 临时 SQLite + 临时端口) |
| G2 | 三套环境是否有必要 | §8.5 环境总结表:开发/CI临时/生产,不需要常驻测试环境 |
| G3 | schema 变更检查可以,可选+开关 | §8.5 补充 schema 检查说明:可选 + 配置开关强制绕过 |
| G4 | 分支保护确认开启 | §14 更新为已确认 |
| G5 | E2E 触发方式 | §8.1 更新:CI 自动 + 手动触发 |
### v0.5 → v0.6 修订清单(主公三次审阅)
| 编号 | 反馈 | 修订内容 |
|------|------|----------|
| H1 | Agent spawn 走生产 openclaw,不隔离 | §8.5 新增隔离边界表:程序逻辑隔离 vs Agent 走生产 |
| H2 | 路径硬编码问题 | §8.5 新增路径硬编码修复清单(6 处,2 处已支持、4 处待修) |
### v0.6 → v1.0 定稿(司马懿终审通过)
- 隔离边界表增加"缓解措施"列(仲达建议)
- 路径硬编码清单增加"行数"列(仲达建议)
- 状态标记为 v1.0 定稿
### v1.0 → v1.1(主公要求:补全局工作流串联图)
- §9.0 新增全局工作流串联图:从问题入口到部署上线的完整链路
- 每个节点标注:谁做、用什么工具、什么Skill、输入输出
- 标准流程 10 步 + Hotfix 分支 4 步
- 新增工具链衔接总览表
- 新增衔接机制详解:每一步怎么触发下一步、靠什么技术、怎么通知
- §9.0.1 新增 5 个通知模板(任务指派/CI失败/Review不通过/部署回滚/Bug确认)
- 修正:Agent 不看 Gitea PR 评论,所有需 Agent 行动的场景都走 Mail 推送
### v1.2 → v2.0(串联架构重构)
- §15 新增完整串联架构:Gitea 自动化 + Daemon Webhook 模块 + Mail 执行层
- 核心变更:ci-notifier 取消,Webhook 转发集成到 daemon
- 新增:超时检测(三级处置)+ 失败处置(走 Issue 流程)
- 新增:Mail 模板(流程强约束固化)
- 新增:Skill vs Mail 边界明确
- 新增:Agent ID ↔ Git 用户名映射
- §9.0 衔接机制表更新:所有 ci-notifier 引用替换为 daemon Webhook 模块
- §9.0.1 通知模板 From 字段更新:ci-notifier → daemon-webhook
### v2.0 司马懿终审修订
| 编号 | 类型 | 问题 | 修订内容 |
|------|------|------|----------|
| M1 | 必须修 | 超时阈值统一 4h 不合理 | §15.4 改为从 Mail metadata 读取个性化 deadline,三级处置按超时程度而非绝对时间 |
| S1 | 建议 | Webhook 代码缺少映射步骤 | §15.3 代码示例加 to_agent_id() 映射函数,所有 send_mail 调用都经映射 |
| S2 | 建议 | Webhook 缺签名验证 | §15.3 代码示例加 X-Gitea-Signature HMAC 验证 |
| S3 | 建议 | 缺 Bug 确认模板 | §15.5 新增模板 6(Bug 确认通知) |
- §14 待讨论更新:已确认事项标记,新增 Webhook 模块待讨论项
### v2.0 实施记录(2026-06-06
| # | 实施项 | 状态 | 备注 |
|---|--------|------|------|
| 1 | Gitea Actions 能力调研 | ✅ | docs/research/gitea-actions-research.md |
| 2 | Mail API 回复能力调研 | ✅ | docs/research/mail-reply-research.md |
| 3 | 路径硬编码修复 | ✅ | 4处改为环境变量,411 tests passed |
| 4 | CI workflow 编写 | ✅ | ci.yml + deploy.yml + e2e.yml |
| 5 | Skill 文件编写 | ✅ | 7个共通技能 |
| 6 | Gitea 仓库创建 | ✅ | sanguo/moziplus-v2(姜维) |
| 7 | act-runner 部署 | ✅ | arm64 裸机 + LaunchAgent(姜维) |
| 8 | 分支保护配置 | ✅ | main: CI通过+1人Review+禁止force push(姜维) |
| 9 | Webhook 配置 | ✅ | 已配未启用(等 daemon 集成)(姜维) |
| 10 | CI Secret 配置 | ✅ | CI_TOKEN(注意:Gitea 保留 GITEA_* 前缀) |
| 11 | Workflow 变量前缀验证 | ✅ | gitea.* 正确,无需修改 |
| 12 | Mail 模板验证 | ✅ | 5项能力全验证通过 |
| 13 | PR #1 创建 | ✅ | feat/initial-setup → main |
| 14 | 开发→安装目录同步 | ✅ | diff 确认一致 |
| - | Webhook daemon 集成 | ⏳ | 需改 daemon 代码,暂不做 |
| - | Mail 模板自动发送(Webhook 触发) | ⏳ | 依赖 Webhook daemon 集成 |
| - | act-runner LaunchAgent 网络就绪优化 | ⏳ | 偶尔 gRPC 连接失败 |
| - | Gitea 升级到 v1.24+ | 💡 | 建议,非必须 |
---
## §13. 项目维度组织
### 13.1 多项目并行
所有内容以项目为单位组织:
| 维度 | 按项目组织 |
|------|------------|
| **代码仓库** | Gitea 上每个项目一个 repo |
| **Issue** | 每个项目的 Issue 独立管理 |
| **CI/CD** | 每个项目自管 `.gitea/workflows/` |
| **部署脚本** | 每个项目自管 `scripts/deploy.sh` |
| **测试** | 每个项目自管 `tests/` 目录 |
| **Skill 配置** | 通用 Skill 共享,项目特定 Skill 放项目仓库 |
特殊:`_general` 项目用于跨项目的通用任务(如工具链基础设施维护)。
---
## §14. 待讨论
1. **Gitea admin 权限**:姜维持有 admin 权限,可负责启用 Actions、配置分支保护等
2. **分支保护**:已确认开启——PR 必须 CI 通过 + 至少 1 人 Review 才能合并到 main
3. **数据库 schema 变更检查**:CI 自动检测破坏性变更(可选,有开关可强制绕过),检测不到的提 Issue 人工干预 — **已确认**
4. **环境架构**:三环境(开发/CI临时/生产),CI 临时环境支持 E2E 自动跑不污染生产 — **已确认**
5. **Webhook 转发模块集成到 daemon**:已确认方向,具体实现待设计(见 §15)
---
## §15. 串联架构
> §15 是 v2.0 的核心新增章节,定义工具链三层串联架构:Gitea 自动化 + Daemon Webhook 模块 + Mail 执行层。
> 此架构取消了之前的 ci-notifier 虚拟角色概念,所有事件流转通过三层清晰分工完成。
### §15.1 串联架构总览
三层架构图:
```
Layer 1: Gitea 自动化(流程引擎)
- Actions: push → CI, PR → CI, merge → deploy
- Status Check: CI 结果自动显示在 PR
- Branch Protection: CI通过+Review通过才许merge
- CI workflow 内可调 Gitea API 写 PR 评论
Layer 2: Daemon 流程编排(转发+监控)
- Webhook 接收模块:监听 /webhook 端点
- 简单事件:Webhook → 解析 → 直接发 Mail
- 复杂事件:Webhook → 解析 → spawn 庞统作为任务节点
- 超时检测:在 ticker 中扫描未回复 Mail
- 失败处置:轻度重发提醒,中度 spawn 庞统,严重创建 Issue
Layer 3: Mail 执行层(Agent 接口)
- Mail = 流程指令 + 回复确认 + 时限
- Agent 收到 Mail 后行动,完成后回复确认
- Mail 线程 = Issue 生命周期可追溯
- 流程强约束固化在 Mail 模板中
```
核心原则:
1. **Gitea 是流程引擎**——所有状态流转靠 Gitea 自己(Actions + Status Check + Branch Protection
2. **Webhook → Mail 转发集成在 daemon 里**——状态共享,可直接触发下一步
3. **Mail 是 Agent 接口**——Agent 不看 Gitea UI,所有需 Agent 行动的事件通过 Mail 推送
4. **简单事件 daemon 直接处理,复杂事件(多 Agent 协作)spawn 庞统作为协调节点**
5. **没有 ci-notifier**——不需要虚拟角色,Gitea 是流程引擎,daemon 是翻译器+监控器
### §15.2 Gitea Webhook 能力清单
| 事件类型 | 触发时机 | Payload 关键字段 | 工具链用途 |
|---------|---------|----------------|-----------|
| `push` | 代码推送 | commit hash, 分支, 作者 | 不需要转发(Actions 自动处理) |
| `pull_request` (opened) | PR 创建 | PR ID, 标题, 分支, 作者 | → Mail 通知司马懿 Review |
| `pull_request_review` (submitted) | Review 提交 | PR ID, 审查者, 结论(APPROVE/REQUEST_CHANGES), 评论 | → Mail 通知张飞 Review 结果 |
| `pull_request` (closed/merged) | PR 合并 | PR ID, 合并 commit | 不需要转发(Actions 自动触发 deploy |
| `issue_comment` | PR/Issue 评论 | 评论者, 内容 | CI workflow 写的失败评论 → 转发 Mail |
| `issues` (opened+assigned) | Issue 创建/指派 | Issue ID, 标题, 被指派人 | → Mail 通知开发者 |
| `release` | Release 创建 | tag, 名称 | 触发完整 CI+部署 |
> **说明**Webhook 是单向 POSTGitea 发完不管。需要在 daemon 里实现 `/webhook` 端点接收。
### §15.3 Daemon Webhook 模块设计
在 daemon 中新增一个 Webhook 接收模块(FastAPI 路由):
```python
# src/api/webhook_routes.py
@router.post("/webhook/gitea")
async def handle_gitea_webhook(event: dict, x_gitea_event: str = Header(...), x_gitea_signature: str = Header(None)):
"""接收 Gitea Webhook,翻译成 Mail 或 spawn 庞统"""
# 签名验证(防止伪造)
if x_gitea_signature and WEBHOOK_SECRET:
expected = hmac.new(WEBHOOK_SECRET, json.dumps(event).encode(), sha256).hexdigest()
if not hmac.compare_digest(expected, x_gitea_signature):
raise HTTPException(403, "Invalid webhook signature")
# Git 用户名 → Agent ID 映射
def to_agent_id(git_username: str) -> str:
return GIT_TO_AGENT.get(git_username, git_username)
if x_gitea_event == "pull_request":
action = event["action"]
if action == "opened":
pr_author = to_agent_id(event["pull_request"]["user"]["login"])
await send_mail(to="simayi-challenger", title=f"Review 请求: PR #{event['number']}", ...)
elif action == "closed" and event["pull_request"]["merged"]:
# merge 不需要通知,Actions 自动处理
pass
elif x_gitea_event == "pull_request_review":
state = event["review"]["state"]
if state == "APPROVED":
pr_author = to_agent_id(event["pull_request"]["user"]["login"])
await send_mail(to=pr_author,
title=f"Review 通过: PR #{event['number']},请 merge", ...)
elif state == "REQUEST_CHANGES":
pr_author = to_agent_id(event["pull_request"]["user"]["login"])
await send_mail(to=pr_author,
title=f"Review 不通过: PR #{event['number']}", ...)
elif x_gitea_event == "issue_comment":
# CI 自动写的失败评论 → 转发给相关 Agent
if "CI 失败" in event["comment"]["body"]:
commenter = to_agent_id(event["sender"]["login"])
await send_mail(to=commenter,
title=f"CI 失败: PR #{event['number']}", ...)
elif x_gitea_event == "issues":
if event["action"] == "assigned":
assignee = to_agent_id(event["assignee"]["login"])
await send_mail(to=assignee,
title=f"任务指派: Issue #{event['number']}", ...)
```
关键设计:
- 模块部署在 daemon 的 FastAPI 里(已有服务,只加路由)
- 和 Mail API 共享 SQLite(直接调内部函数,不经过 HTTP)
- 简单事件(CI失败通知、Review通知、任务指派)直接发 Mail
- 复杂事件(部署失败、架构级 Review 问题)spawn 庞统
### §15.4 回复驱动与超时处置
> daemon ticker 中新增两个定时扫描任务,**回复驱动是主路径,超时检测是兜底**。
**主路径:回复驱动(Agent 回复 Mail 后,daemon 根据结果触发下一步)**
```python
# daemon ticker 每小时跑一次
async def process_flow():
# 路径1:扫描新回复,驱动下一步(主路径)
await check_replies()
# 路径2:扫描超时未回复,兜底处置
await check_stale_flows()
async def check_replies():
"""扫描 Mail 回复,根据结果触发下一步"""
# 查所有有新回复的 Mail(回复在上一轮扫描之后)
replied_mails = queries.find_recently_replied(since=last_scan)
for mail in replied_mails:
reply = get_reply(mail.id)
if is_success_reply(reply):
# 成功 → 记录闭环,不触发动作
# 原因:Gitea 自动化(Actions + Webhook)会自动推动下一步
# 例:张飞回复"已创建PR" → Gitea Webhook 自动通知司马懿 Review
# 例:张飞回复"已merge" → Gitea Actions 自动触发 deploy
log.info(f"Mail {mail.id} 已成功闭环: {reply.text[:100]}")
elif is_failure_reply(reply):
# 失败 → 创建 Issue 走正式流程
await create_issue(
title=f"执行失败: {mail.title}",
labels=["bug"],
body=f"原指令: {mail.text}\n\n失败原因: {reply.text}"
)
# 通知庞统
await send_mail(to="pangtong-fujunshi",
title=f"执行失败已创建 Issue: {mail.title}", ...)
elif is_help_reply(reply):
# 需协助 → spawn 庞统协调
await spawn_agent("pangtong-fujunshi",
f"Agent {reply.from} 执行 '{mail.title}' 遇到困难,请求协助。详情: {reply.text}")
```
**兜底路径:超时检测(Agent 没回复时的处置)**
```python
async def check_stale_flows():
"""扫描超时未回复的 Mail,按个性化 deadline 判断"""
pending_mails = queries.find_pending_requests()
for mail in pending_mails:
deadline_hours = mail.meta.get("deadline_hours", 8) # 默认 8 小时
hours_elapsed = (now() - mail.created_at).total_seconds() / 3600
if hours_elapsed < deadline_hours:
continue # 未超时,跳过
hours_overdue = hours_elapsed - deadline_hours
if hours_overdue < 4:
# 轻度:重发 Mail 提醒
await send_mail(to=mail.to, title=f"提醒: {mail.title}",
text=f"此任务已超时 {hours_overdue:.0f} 小时,请尽快处理。")
elif hours_overdue < 16:
# 中度:spawn 庞统介入
await spawn_agent("pangtong-fujunshi",
f"流程卡住: Mail {mail.id} '{mail.title}' 超时 {hours_overdue:.0f} 小时未回复。请介入协调。")
else:
# 严重:创建 Issue + 通知庞统
await create_issue(title=f"流程严重超时: {mail.title}", labels=["blocked", "priority:high"])
await send_mail(to="pangtong-fujunshi",
title=f"严重超时: {mail.title},已创建 Issue", ...)
```
**失败处置原则**
- 所有失败 → Issue 走正式流程,不私下通知 Agent 悄悄解决
- CI 失败 → CI workflow 写 PR 评论 → Webhook → Mail 通知 → Agent 修 → 走标准 PR 流程
- 部署失败回滚 → CI workflow 创建 Issuepriority:high → 走标准 Bug 修复流程
- 超时未响应 → 三级处置(提醒→庞统介入→Issue+通知主公)
**断点续传**
- 每个环节的状态都在 Gitea 上(PR 状态、CI 结果、Review 状态)
- Mail 线程提供完整的可追溯记录
- 回复驱动确保正常流程自动推进,超时检测确保异常被捕获
- 流程卡住时,庞统可以根据 Mail 线程 + Gitea 状态快速定位断点并继续
### §15.5 Mail 模板(流程强约束固化)
> **注意**:Mail 模板里固化的是**流程强约束**——这个工作链特有的一步步怎么做。
> 共通技能(git 操作、审查清单等)放在 Skill 里(见 §10)。
**模板 1:任务指派(庞统/daemon → 开发者)**
```
Title: 任务指派: Issue #{N} - {标题}
Text:
Issue: {url}
分支: {branch_name}
风险级别: {level}
验收标准: {criteria}
流程:
1. 创建分支 {branch_name}
2. 编码 + 写 UT
3. push → 等 CICI 会自动跑)
4. CI 通过后创建 PRGitea API: POST /repos/{owner}/{repo}/pulls
5. 等 Review(司马懿会自动收到通知)
6. Review 通过后 merge PR
如果 CI 失败:修复后重新 push,回到步骤 3
如果 Review 不通过:按意见修改后重新 push,回到步骤 3
完成后回复此 Mail 确认。
时限:{deadline}
```
**模板 2Review 请求(Webhook → 司马懿)**
```
Title: Review 请求: PR #{N}
Text:
PR: {url}
标题: {title}
风险级别: {level}
改动文件: {file_list}
流程:
1. 读取 PR diffGitea API: GET /repos/{owner}/{repo}/pulls/{N}.diff
2. 按审查清单审查(参考 code-review Skill
3. 提交 ReviewGitea API: POST /repos/{owner}/{repo}/pulls/{N}/reviews
- APPROVE 或 REQUEST_CHANGES
4. 提交后张飞会自动收到通知
完成后回复此 Mail 确认。
时限:{deadline}
```
**模板 3Review 结果通知(Webhook → 张飞)**
```
Title: Review {result}: PR #{N}
Text:
PR: {url}
结果: {APPROVED / REQUEST_CHANGES}
审查意见: {review_body}
{如果通过}:
流程:merge PRGitea API: POST /repos/{owner}/{repo}/pulls/{N}/merge
{如果不通过}:
流程:
1. 按审查意见修改代码
2. push → CI 自动跑
3. CI 通过后等重新 Review
完成后回复此 Mail 确认。
```
**模板 4CI 失败通知(Webhook from issue_comment → 张飞)**
```
Title: CI 失败: PR #{N}
Text:
PR: {url}
失败测试: {failed_tests}
错误摘要: {error_summary}
流程:
1. 查看完整 CI 日志(PR 页面)
2. 修复失败的测试
3. push → CI 自动重跑
完成后回复此 Mail 确认。
```
**模板 5:部署失败通知(→ 庞统,复杂事件 spawn)**
```
Title: 部署失败已回滚: {repo}
Text:
仓库: {repo}
触发 commit: {sha}
失败原因: {error}
已自动回滚到: {previous_version}
已自动创建 Issue #{M}。
请协调排查。
```
**模板 6:Bug 确认通知(司马懿 → 改动者)**
```
Title: Bug 确认: Issue #{N} - {标题}
Text:
Issue: {url}
根因: {根因描述}
复现步骤: {steps}
影响范围: {scope}
风险级别: {level}
分支: fix/issue-{N}-{简述}
流程:
1. 创建分支 fix/issue-{N}-{简述}
2. 修复 bug + 写回归测试
3. push → CI 自动跑
4. CI 通过后创建 PR
5. 等 Review
完成后回复此 Mail 确认。
时限:{deadline}
```
### §15.6 Skill vs Mail 边界
| 维度 | Mail 模板 | Skill |
|------|----------|-------|
| 作用域 | 工具链流程特有 | 全局,所有项目通用 |
| 强制度 | 强约束,Agent 必须按流程走 | 按需加载,提升质量 |
| 内容 | 流程步骤+Gitea API 调用指令 | 共通技能(审查清单、git规范等) |
| 示例 | "CI失败→修→push→等CI→等Review" | "怎么写好 UT"、"审查清单" |
| 没有 Skill 能否运转 | ✅ 能(流程不会断) | 只是质量没那么高 |
**原则:没有 Skill,流程还能运转。有了 Skill,流程运转得更好。**
Skill 只放共通的、所有项目都能用的技能:
- `git-workflow`:分支命名、commit 规范(所有项目通用)
- `code-review`:审查清单(所有项目通用)
- `testing-workflow`:测试规范(所有项目通用)
- `bugfix-workflow`:根因定位方法(所有项目通用)
工具链特有的流程("CI 失败后怎么做"、"Review 通过后怎么做")全部固化在 Mail 模板里。
### §15.7 Agent ID ↔ Git 用户名映射
CI workflow 和 Webhook 中的用户标识是 Git 用户名,Mail API 需要 Agent ID
| Git 用户名 | Agent ID |
|-----------|----------|
| zhangfei | zhangfei-dev |
| jiangwei | jiangwei-infra |
| simayi | simayi-challenger |
| pangtong | pangtong-fujunshi |
| guanyu | guanyu-dev |
| zhaoyun | zhaoyun-data |
daemon Webhook 模块中维护此映射表。
---
## §16. 事件中枢详细设计
> §16 是对 §15 串联架构的落地设计补充,定义事件中枢的具体实现方案。
> 版本: v1.0-draft | 日期: 2026-06-07 | 状态: 待评审
### §16.0. 讨论纪要
本节记录设计过程中的关键讨论,供未来回溯。
### 两条线的定位(主公决策)
| 线 | 方向 | 机制 | 定位 |
|----|------|------|------|
| **出线** | Agent → Gitea | Agent 直接操作 Gitea,完成后发 Mail 通知下一个 Agent | Gitea = 黑板,Mail = 触发器 |
| **入线** | 外部 → Agent | 外部事件(CI/部署/Webhook)→ 事件中枢 → Mail → Agent | 事件中枢 = 入口 |
**原则 1**:Agent 之间的协作不走事件中枢。中枢只处理"外部 → Agent"。
**原则 2**:所有工具链留痕在 Gitea,Mail 是辅助推送。即使 Mail 失败,Agent 可主动查 Gitea 恢复上下文。
**原则 3**:工具链反馈 Agent 的通知统一走事件中枢,避免散乱。
### 关键决策记录
| # | 决策 | 理由 |
|---|------|------|
| D1 | 不做第三种 task 类型 | 中枢产出就是 Mailfrom=system, type=inform),模板填充 description,投递复用 spawner。区别只在内容(模板化)和元数据(结构化),不需要新的数据结构 |
| D2 | 提示词独立模板文件 | 模板和代码分离,改文案不改代码,可审查可扩展 |
| D3 | 中枢是 daemon 内模块 | 要访问 Blackboard 创建 Task,共享进程。用独立路由模块(`toolchain_routes.py`)保持职责清晰 |
| D4 | 同步处理 | 事件处理很轻(解析+模板填充+创建Task),远在 Gitea 5秒超时内。复杂事件未来用 asyncio.create_task |
| D5 | Agent ID ≠ Gitea 用户名,需要映射表 | Gitea 注册用户名是短名(zhangfei),Agent ID 是长名(zhangfei-dev)。§4.4 的 `to_agent_id()` 内建 `GIT_TO_AGENT` 映射字典(见 §15.7 |
| D6 | CI/部署通知也走事件中枢 | 统一入口,不走 workflow 直接调 Mail API。CI workflow 写 PR comment → Gitea 触发 issue_comment Webhook → 中枢处理 |
---
### §16.1. 定位与边界
### 1.1 是什么
事件中枢是 daemon 内的一个路由模块,负责将**外部工具链事件**翻译成 Mail 通知推送给 Agent。
### 1.2 不是什么
- 不是消息队列
- 不是 Agent 间通信通道(Agent 间协作走 Mail,不经过中枢)
- 不是编排引擎(任务编排由 dispatcher + spawner 负责)
- 不负责 Agent 回复 Mail 后的流程驱动(由 daemon ticker 的回复驱动机制负责,见 §15.4)
### 1.3 两条线
```
出线(Agent → 工具链):
Agent 直接操作 Gitea(创建 Issue/PR/Review 等)
→ 完成后发 Mail 通知下一个 Agent
→ 下一个 Agent 去读 Gitea 继续干活
入线(工具链 → Agent):
外部事件(CI 结果/部署结果/Webhook 事件)
→ 事件中枢
→ 模板化 Mail
→ Agent 收到通知,去 Gitea 查看详情并行动
```
### 1.4 数据流
```
外部事件源 事件中枢 Agent
───────── ────── ──────
Gitea Webhook ─────┐
CI workflow ───────┤ toolchain_routes.py Mail Task
(PR comment) ────┤ ┌─────────────────┐ (from: system)
├──→│ 1. 接收事件 │ │
Deploy workflow ──┤ │ 2. 验签/过滤 │ ↓
(PR comment) ────┤ │ 3. 幂等检查 │ dispatcher
│ │ 4. 选模板+填充 │ │
daemon 内部 ───────┘ │ 5. 创建 Mail │ ↓
(预留接口) └─────────────────┘ spawner 投递
```
---
### §16.2. 事件分类与处理
### 2.1 事件来源
| 来源 | 触发方式 | 事件类型 |
|------|---------|---------|
| Gitea Webhook | Gitea 主动 POST | PR opened, Review submitted, Issue assigned, issue_comment |
| CI workflow | CI 写 PR comment → 触发 issue_comment Webhook | CI 失败 |
| Deploy workflow | Deploy 写 PR comment → 触发 issue_comment Webhook | 部署成功/失败 |
| daemon 内部 | 内部函数调用 | 预留,当前不走中枢 |
### 2.2 事件处理矩阵
| 事件 | 来源 | 通知谁 | 模板 | 关键信息 |
|------|------|--------|------|---------|
| `pull_request` opened | Gitea Webhook | 司马懿(Review | `review_request.md` | PR号、标题、作者、分支、文件列表、风险级别 |
| `pull_request_review` submitted | Gitea Webhook | PR 作者 | `review_result.md` | PR号、审查结论、评论、审查者 |
| `issues` assigned | Gitea Webhook | 被指派人 | `issue_assigned.md` | Issue号、标题、标签、描述、分支名建议 |
| `issue_comment` (CI失败) | CI workflow → Webhook | PR 作者 | `ci_failure.md` | PR号、分支、失败步骤、错误摘要 |
| ~~部署成功~~ | ~~不需要通知~~ | ~~流程自动闭环~~ | ~~—~~ | ~~Gitea Issue 自动关闭~~ |
| `issue_comment` (部署失败) | Deploy workflow → Webhook | 庞统 + 姜维 | `deploy_failure.md` | 仓库、失败原因、已回滚版本 |
### 2.3 事件过滤规则
不是所有事件都需要处理:
| 规则 | 说明 |
|------|------|
| 只处理白名单内的事件类型 | 未知的忽略 + 日志 |
| issue_comment 需判断来源 | 只处理 CI/deploy workflow 写的评论(按特定前缀匹配,如 `[CI]``[Deploy]` |
| PR 作者/审查者必须是已知 Agent | 未知的忽略 + 日志 |
| 幂等:同一事件不重复创建 Mail | 按 `{x_gitea_event}-{x_gitea_delivery}` 去重(delivery ID 来自 `X-Gitea-Delivery` header |
---
### §16.3. 模板系统
### 3.1 模板文件组织
```
templates/toolchain/
├── review_request.md # PR opened → 司马懿 Review
├── review_result.md # Review submitted → PR 作者
├── issue_assigned.md # Issue assigned → 开发者
├── ci_failure.md # CI 失败 → PR 作者
└── deploy_failure.md # 部署失败 → 庞统+姜维
```
### 3.2 模板格式
模板使用 `{variable}` 占位符,中枢填充后生成 Mail description。
**示例:`review_request.md`**
```markdown
PR Review 请求
PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number}
标题: {pr_title}
作者: {pr_author}
分支: {branch}
风险级别: {risk_level}
改动文件:
{file_list}
流程:
1. 读取 PR diffGitea API: GET /repos/{repo}/pulls/{pr_number}.diff
2. 按审查清单审查(参考 code-review Skill
3. 提交 ReviewGitea API: POST /repos/{repo}/pulls/{pr_number}/reviews
4. 提交后改动者会自动收到通知
完成后回复此 Mail 确认。
```
### 3.3 模板变量提取
| 变量 | 来源 | 提取方式 |
|------|------|---------|
| `pr_number` | Webhook payload | `event["pull_request"]["number"]` |
| `pr_title` | Webhook payload | `event["pull_request"]["title"]` |
| `pr_author` | Webhook payload | `to_agent_id(event["pull_request"]["user"]["login"])` |
| `branch` | Webhook payload | `event["pull_request"]["head"]["ref"]` |
| `file_list` | Webhook payload | `event["pull_request"]["changed_files"]` 或需要额外 API 调用 |
| `risk_level` | daemon 计算 | 按文件路径规则匹配(见 §3.4) |
| `repo` | Webhook payload | 从 `event["repository"]["full_name"]` 提取 |
### 3.4 风险级别自动判定(简化版)
按改动文件路径匹配规则:
```python
from pathlib import PurePath
# 高风险文件路径模式(使用 pathlib.PurePath.match,支持 ** 递归)
HIGH_PATTERNS = ["**/spawner*", "**/ticker*", "**/dispatcher*",
"**/router*", "**/guardrails*", "**/strategy*", "**/risk*"]
def calc_risk_level(changed_files: list[str]) -> str:
for f in changed_files:
for pattern in HIGH_PATTERNS:
if PurePath(f).match(pattern):
return "high"
return "standard"
```
---
### §16.4. 技术设计
### 4.1 模块结构
```
src/api/
├── toolchain_routes.py # 事件中枢路由(~150行)
├── mail_routes.py # 现有 Mail API
└── ...
src/daemon/
├── toolchain_templates.py # 模板加载+填充(~80行)
├── mail_notify.py # 现有 Mail 失败通知
└── ...
templates/toolchain/
├── review_request.md
├── review_result.md
├── issue_assigned.md
├── ci_failure.md
├── deploy_success.md
└── deploy_failure.md
```
### 4.2 toolchain_routes.py 接口设计
```python
# src/api/toolchain_routes.py
from fastapi import APIRouter, Request, Header, HTTPException
from src.daemon.toolchain_templates import TemplateEngine
router = APIRouter()
engine = TemplateEngine()
GITEA_WEBHOOK_SECRET = os.environ.get("GITEA_WEBHOOK_SECRET", "")
@router.post("/webhook/gitea")
async def handle_gitea_webhook(
request: Request,
x_gitea_event: str = Header(...),
x_gitea_signature: str = Header(None),
x_gitea_delivery: str = Header(None),
):
"""接收 Gitea Webhook,翻译成 Mail"""
body = await request.body()
# 1. 签名验证(可选)
if GITEA_WEBHOOK_SECRET:
expected = hmac.new(GITEA_WEBHOOK_SECRET.encode(), body, sha256).hexdigest()
if not hmac.compare_digest(expected, (x_gitea_signature or "")):
raise HTTPException(403, "Invalid signature")
event = json.loads(body)
# 2. 幂等检查(delivery ID = X-Gitea-Delivery headerGitea 全局唯一)
event_key = f"{x_gitea_event}-{x_gitea_delivery}"
if is_duplicate(event_key):
return {"status": "duplicate"}
# 3. 路由到对应处理器
handler = HANDLERS.get(x_gitea_event)
if not handler:
logger.info("Ignoring unhandled event: %s", x_gitea_event)
return {"status": "ignored"}
# 4. 处理事件 → 创建 Mail
try:
result = await handler(engine, event)
return {"status": "ok", "mail_id": result}
except Exception as e:
logger.exception("Failed to handle %s event", x_gitea_event)
raise HTTPException(500, str(e))
```
### 4.3 事件处理器
每个事件类型一个处理函数,职责:解析 payload → 选模板 → 填充 → 创建 Mail Task。
```python
async def handle_pull_request(engine: TemplateEngine, event: dict) -> str:
"""pull_request 事件分发:只处理 opened,忽略 synchronized/closed/reopened"""
if event.get("action") != "opened":
return None
return await _handle_pr_opened(engine, event)
async def _handle_pr_opened(engine: TemplateEngine, event: dict) -> str:
"""PR opened → Review 请求给司马懿"""
pr = event["pull_request"]
# 提取变量
variables = {
"pr_number": pr["number"],
"pr_title": pr["title"],
"pr_author": to_agent_id(pr["user"]["login"]),
"branch": pr["head"]["ref"],
"repo": event["repository"]["full_name"],
"repo_owner": event["repository"]["owner"]["login"],
"risk_level": calc_risk_level(get_changed_files(pr)),
"file_list": format_file_list(get_changed_files(pr)),
}
# 填充模板
text = engine.render("review_request.md", variables)
# 创建 Mail Task
meta = {
"source": "toolchain",
"webhook_event": "pull_request_opened",
"pr_number": pr["number"],
"repo": variables["repo"],
}
return create_mail_task(
to="simayi-challenger",
title=f"Review 请求: PR #{pr['number']} {pr['title']}",
text=text,
meta=meta,
)
async def handle_review_submitted(engine: TemplateEngine, event: dict) -> str:
"""Review submitted → 结果通知 PR 作者"""
review = event["review"]
pr = event["pull_request"]
pr_author = to_agent_id(pr["user"]["login"])
state = review["state"] # APPROVED / REQUEST_CHANGES / COMMENTED
if state == "COMMENTED":
return None # 普通评论不通知
variables = {
"pr_number": pr["number"],
"pr_title": pr["title"],
"result": "通过" if state == "APPROVED" else "不通过",
"reviewer": to_agent_id(review["user"]["login"]),
"review_body": review["body"] or "",
"repo": event["repository"]["full_name"],
"repo_owner": event["repository"]["owner"]["login"],
}
template = "review_result.md"
text = engine.render(template, variables)
return create_mail_task(
to=pr_author,
title=f"Review {variables['result']}: PR #{pr['number']}",
text=text,
meta={"source": "toolchain", "webhook_event": "review_submitted", "pr_number": pr["number"]},
)
async def handle_issue_comment(engine: TemplateEngine, event: dict) -> str:
"""issue_comment → 判断来源,路由到 CI/部署通知"""
comment_body = event["comment"]["body"]
if comment_body.startswith("[CI]"):
return await handle_ci_comment(engine, event)
elif comment_body.startswith("[Deploy"):
return await handle_deploy_comment(engine, event)
return None # 非 CI/部署评论不处理
# HANDLERS 注册表
# pull_request 和 issues 只处理特定 action,在函数内部过滤
HANDLERS = {
"pull_request": handle_pull_request, # 内部只处理 opened,忽略其他 action
"pull_request_review": handle_review_submitted,
"issues": handle_issues, # 内部只处理 assigned,忽略其他 action
"issue_comment": handle_issue_comment,
}
```
### 4.4 共用函数
```python
# Git 用户名 → Agent ID 映射(与 §15.7 一致)
GIT_TO_AGENT = {
"pangtong": "pangtong-fujunshi",
"zhangfei": "zhangfei-dev",
"simayi": "simayi-challenger",
"guanyu": "guanyu-dev",
"zhaoyun": "zhaoyun-data",
"jiangwei": "jiangwei-infra",
}
def to_agent_id(gitea_username: str) -> str:
"""Gitea 用户名 → Agent ID(通过映射表)"""
return GIT_TO_AGENT.get(gitea_username, gitea_username)
def create_mail_task(to: str, title: str, text: str, meta: dict) -> str:
"""创建 Mail Taskfrom=system, type=inform
复用 mail_notify.py 的模式:直接通过 Blackboard 创建 Task。
"""
# 从 mail_routes.py 提取共用逻辑,或直接复用 mail_notify 的方式
...
def is_duplicate(event_key: str) -> bool:
"""幂等检查:同一事件不重复创建 Mail
存储在 SQLite events 表,daemon 重启后仍有效。
保留最近 7 天记录,ticker 定期清理过期记录。
"""
...
def calc_risk_level(files: list[str]) -> str:
"""按文件路径规则判定风险级别"""
...
def get_changed_files(pr: dict) -> list[str]:
"""从 PR payload 提取改动文件列表"""
...
```
### 4.5 模板引擎
```python
# src/daemon/toolchain_templates.py
from pathlib import Path
import string
TEMPLATE_DIR = Path(__file__).parent.parent.parent / "templates" / "toolchain"
class TemplateEngine:
def __init__(self, template_dir: Path = TEMPLATE_DIR):
self.template_dir = template_dir
self._cache = {}
def render(self, template_name: str, variables: dict) -> str:
"""加载模板文件并填充变量"""
if template_name not in self._cache:
path = self.template_dir / template_name
self._cache[template_name] = path.read_text()
template = self._cache[template_name]
return template.format_map(defaultdict(str, variables))
```
---
### §16.5. 错误处理
| 场景 | 处理 | HTTP 返回 |
|------|------|----------|
| 签名验证失败 | 日志 warning | 403 |
| payload 解析失败 | 日志 error | 200(不触发 Gitea 重试) |
| 未知事件类型 | 忽略 + 日志 info | 200 |
| 幂等检测到重复 | 忽略 + 日志 info | 200 |
| 未知 Agent(不在映射表) | 忽略 + 日志 warning | 200 |
| 模板填充失败 | 日志 error | 500(触发 Gitea 重试) |
| Mail 创建失败 | 日志 error | 500(触发 Gitea 重试) |
**原则**
- daemon 自身能处理的错误(格式、过滤、幂等)→ 返回 200,不让 Gitea 无意义重试
- daemon 处理不了的错误(数据库、内部异常)→ 返回 500,让 Gitea 重试
---
### §16.6. CI/Deploy Workflow 配合
### 6.1 CI 失败通知
CI workflow 失败时写 PR comment,触发 issue_comment Webhook
```yaml
# .gitea/workflows/ci.yml
- name: Report failure
if: failure()
run: |
curl -s -X POST \
"http://192.168.2.154:3000/api/v1/repos/{owner}/{repo}/issues/{pr_number}/comments" \
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{
\"body\": \"[CI] CI 失败\\n\\n分支: ${{ gitea.ref_name }}\\n失败步骤: ${{ job.status }}\\n错误摘要: $(tail -20 $GITHUB_STEP_SUMMARY)\"
}"
```
### 6.2 Deploy 结果通知
同理,deploy workflow 成功/失败时写 comment
```yaml
# .gitea/workflows/deploy.yml
- name: Report success
if: success()
run: |
curl -s -X POST \
"http://192.168.2.154:3000/api/v1/repos/{owner}/{repo}/issues/{pr_number}/comments" \
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
-d "{\"body\": \"[Deploy:成功] 版本: $(bash scripts/deploy.sh --version)\\n请确认后关闭 Issue.\"}"
- name: Report failure
if: failure()
run: |
curl -s -X POST \
"http://192.168.2.154:3000/api/v1/repos/{owner}/{repo}/issues/{pr_number}/comments" \
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
-d "{\"body\": \"[Deploy:失败] 已回滚到上一版本。原因: ...\"}"
```
---
### §16.7. 配置
| 环境变量 | 用途 | 默认值 |
|---------|------|--------|
| `GITEA_WEBHOOK_SECRET` | Webhook HMAC 签名密钥(可选) | 空(跳过验签) |
| `BLACKBOARD_ROOT` | Blackboard 数据根目录(已有) | `~/.sanguo_projects/sanguo_moziplus_v2/data` |
| `TOOLCHAIN_TEMPLATES_DIR` | 模板文件目录(可选) | `templates/toolchain/` |
---
### §16.8. 待确认项
| # | 项 | 负责人 | 说明 |
|---|------|--------|------|
| 1 | ~~各 Agent 在 Gitea 上的注册用户名是否和 Agent ID 一致~~ | ~~姜维~~ | **已确认不一致**zhangfei ≠ zhangfei-dev),已使用映射表 |
| 2 | Gitea Webhook 是否已配置 secret | 姜维 | 当前已配未启用 |
| 3 | CI workflow 是否已有写 PR comment 的 step | — | 当前 CI workflow 可能没有 |
| 4 | `from=system` 走 HTTP API 还是只走内部函数 | — | mail_routes.py 当前只在内部函数支持 system |
| 5 | PR changed_files 是否包含在 Webhook payload 中 | — | Gitea v1.23.4 可能需要额外 API 调用 |
---
### §16.9. 实施计划
| 步骤 | 内容 | 依赖 |
|------|------|------|
| 1 | `toolchain_routes.py` 骨架 + Gitea Webhook 接收 + 签名验证 | 无 |
| 2 | `toolchain_templates.py` 模板引擎 | 无 |
| 3 | 6 个模板文件 | 步骤 2 |
| 4 | 4 个事件处理器(PR/Review/Issue/Comment | 步骤 1+3 |
| 5 | 幂等检查(SQLite events 表,保留 7 天) | 步骤 1 |
| 6 | `create_mail_task` 共用函数(从 mail_routes.py 提取) | 无 |
| 7 | CI/Deploy workflow 加 comment step | 步骤 1+4 |
| 8 | Gitea Webhook 启用 + 测试 | 姜维 |
| 9 | 端到端测试 | 步骤 1-8 |
### 9.1 改动量估算
| 文件 | 行数 | 类型 |
|------|------|------|
| `src/api/toolchain_routes.py` | ~200 | 新增 |
| `src/daemon/toolchain_templates.py` | ~60 | 新增 |
| `templates/toolchain/*.md` | ~2006个文件) | 新增 |
| `src/api/mail_routes.py` | ~20 | 修改(提取共用函数) |
| `.gitea/workflows/ci.yml` | ~15 | 修改(加 comment step |
| `.gitea/workflows/deploy.yml` | ~15 | 修改(加 comment step |
| **总计** | **~510** | |
---
### §16.10. 评审检查清单
- [ ] §0 讨论纪要是否准确反映了决策过程
- [ ] §1 两条线的边界是否清晰(出线不走中枢,入线统一走中枢)
- [ ] §2 事件矩阵是否有遗漏
- [ ] §3 模板内容是否完整,变量提取是否正确
- [ ] §4 接口设计是否合理,共用函数提取是否恰当
- [ ] §5 错误处理策略是否完备
- [ ] §6 CI/Deploy workflow 配合方案是否可行
- [ ] §8 待确认项是否完整
- [ ] §9 实施步骤是否合理
### v2.0 → v2.1 变更(事件中枢落地设计)
| 编号 | 变更内容 |
|------|---------|
| §16 | 新增事件中枢详细设计(§16.0-§16.10),基于 §15 串联架构 v2.0 的落地细节 |