3290 lines
136 KiB
Markdown
3290 lines
136 KiB
Markdown
# 三国团队工具链与开发流程设计
|
||
|
||
> **状态**: v3.3 — #19 上下文四层改造合并 + CI 修复 + A13 修订
|
||
> **作者**: 庞统(副军师)🐦
|
||
> **评审**: 司马懿(仲达)🗡️
|
||
> **日期**: 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.26.2(2026-06-11 从 v1.23.4 升级) |
|
||
| 认证 | HTTP + token;admin 账号(姜维持有) |
|
||
| 权限 | 姜维持有 admin 权限(启用 Actions、分支保护、org webhook 等) |
|
||
| 数据库 | SQLite3 |
|
||
| 部署方式 | Docker(NAS 群晖),数据卷 `/volume2/@docker/volumes/gitea-data/_data` |
|
||
|
||
### 2.2 CI/CD:Gitea Actions
|
||
|
||
| 项 | 配置 |
|
||
|----|------|
|
||
| Runner | Mac mini 裸机,gitea-runner v1.0.8(通过 PM2 管理 `sanguo-act-runner`) |
|
||
| 配置文件 | `.gitea/workflows/*.yml`,每个项目自管 |
|
||
| 语法 | 兼容 GitHub Actions(v1.26.2 已验证支持 concurrency groups) |
|
||
| 触发 | push / PR / tag / workflow_dispatch |
|
||
| v1.26 新增 | concurrency groups、re-run failed jobs、可配置 GITEA_TOKEN 权限 |
|
||
| 仍不支持 | `failure()`、`continue-on-error`、`timeout-minutes` |
|
||
|
||
### 2.4 Gitea 基础设施 Setup 记录(2026-06-11 姜维)
|
||
|
||
> 以下为 Gitea 从 v1.23.4 升级到 v1.26.2 的完整操作记录,作为未来参考。
|
||
|
||
#### 2.4.1 升级 v1.23.4 → v1.26.2
|
||
|
||
**升级原因**:v1.23.4 不支持 concurrency groups,导致双倍触发问题无根因解法。
|
||
|
||
**升级步骤**:
|
||
1. 备份:`docker exec sanguo_gitea gitea dump -c /data/gitea/conf/app.ini -f /data/gitea/gitea-backup-pre-v126.zip`(765MB)
|
||
2. 拉取镜像:Mac 上 skopeo 下载 → python docker SDK 远程 load(群晖 Docker Hub 太慢)
|
||
3. 停止旧容器 + rename 保留回滚
|
||
4. 启动新容器(数据库自动迁移 Migration[312]→[326],含 concurrency #323)
|
||
5. 验证:API + Web UI + 仓库数据 + 用户数据
|
||
|
||
**踩坑:群晖内核 3.10 + git 2.52 不兼容**:
|
||
- 根因:git 2.52 使用 `getrandom(2)` syscall,群晖内核 3.10.108 不支持(3.17 才加入)
|
||
- 症状:`git push` 报 `unable to create temporary file: Function not implemented`
|
||
- 修复:entrypoint 脚本在容器启动时自动从本地缓存降级 git 到 2.45.4
|
||
- 持久化:`/data/entrypoint-wrapper.sh` + `/data/git-2.45.4-r0.apk` 在数据卷里,容器重建不丢失
|
||
- 群晖内核**无法通过 DSM 升级**,内核版本跟硬件型号绑定
|
||
|
||
**完整重建命令**:
|
||
```bash
|
||
docker -H tcp://192.168.2.154:2375 run -d \
|
||
--name sanguo_gitea \
|
||
--restart=always \
|
||
-p 3000:3000 \
|
||
-p 2221:22 \
|
||
-v /volume2/@docker/volumes/gitea-data/_data:/data \
|
||
-e GITEA__database__DB_TYPE=sqlite3 \
|
||
-e GITEA__database__PATH=/data/gitea/gitea.db \
|
||
-e GITEA__server__ROOT_URL=http://192.168.2.154:3000/ \
|
||
--entrypoint /bin/sh \
|
||
gitea/gitea:1.26.2 \
|
||
-c '/data/entrypoint-wrapper.sh'
|
||
```
|
||
|
||
#### 2.4.2 act_runner 升级 v0.2.11 → v1.0.8
|
||
|
||
**升级原因**:v0.2.11 的 multi-step job 执行有 bug,Setup Python 和 Lint step 被跳过。
|
||
|
||
**升级步骤**:
|
||
1. 下载 `gitea-runner-1.0.8-darwin-arm64`(从 gitea.com releases)
|
||
2. `codesign --force --sign -` 重签(macOS Gatekeeper 会 SIGKILL 未签名的二进制)
|
||
3. 替换 `/Users/chufeng/bin/act_runner`
|
||
4. PM2 restart `sanguo-act-runner`
|
||
|
||
**注意**:act_runner 通过 **PM2** 管理(`sanguo-act-runner`),不是 launchd。launchd plist 仅为备份。
|
||
|
||
**PM2 常用命令**:
|
||
```bash
|
||
pm2 restart sanguo-act-runner # 重启
|
||
pm2 logs sanguo-act-runner # 查看日志
|
||
pm2 show sanguo-act-runner # 详情
|
||
```
|
||
|
||
#### 2.4.3 CI Workflow 配置
|
||
|
||
**三个 workflow 文件**:
|
||
|
||
| 文件 | 触发 | concurrency | 说明 |
|
||
|------|------|-------------|------|
|
||
| `ci.yml` | `pull_request` | `group: ci-${{ gitea.ref }}, cancel-in-progress: true` | 同一 PR 新 push 自动取消旧 run |
|
||
| `deploy.yml` | `push to main` | `group: deploy-${{ gitea.ref }}, cancel-in-progress: false` | 部署排队不取消 |
|
||
| `e2e.yml` | `workflow_dispatch` | `group: e2e-${{ gitea.ref }}, cancel-in-progress: true` | 手动触发 |
|
||
|
||
**Branch Protection(main 分支)**:
|
||
- 禁止直接 push
|
||
- status check:`CI / lint (pull_request)` 必须通过
|
||
- 至少 1 人 Review
|
||
|
||
**⚠️ 踩坑**:v1.26 上报的 commit status context 格式变了:
|
||
- 旧格式:`lint`
|
||
- 新格式:`CI / lint (pull_request)`
|
||
- branch protection 必须用新格式匹配,否则 merge 报 "Not all required status checks successful"
|
||
|
||
#### 2.4.4 Org Webhook 配置
|
||
|
||
- **对象**:Gitea 组织 `sanguo` webhook id=28
|
||
- **URL**:`http://192.168.2.153:8083/webhook/gitea`
|
||
- **事件**:16 个(push/issues/PR/PR review 等)
|
||
|
||
**⚠️ 踩坑**:Gitea v1.26 的 PATCH hooks API,只传 `{"active": true}` 会把 events 重置为 `["push"]`。**必须每次 PATCH 都带上完整的 events 列表。**
|
||
|
||
**临时措施(已恢复)**:2026-06-10 曾临时关闭 webhook(CI 错误大爆炸期间),2026-06-11 已恢复。
|
||
|
||
#### 2.4.5 凭据管理
|
||
|
||
| 凭据 | 用途 | 持有者 |
|
||
|------|------|--------|
|
||
| Gitea admin:cf7561523 | 仓库管理、branch protection、org webhook | 姜维 |
|
||
| Gitea PAT (jiangwei-infra) | API 操作、git clone/push | 姜维 |
|
||
| Gitea PAT (cfdaily) | CI workflow 中的 git 操作 | CI secrets |
|
||
|
||
#### 2.4.6 备份与回滚
|
||
|
||
| 项目 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| Gitea 数据库备份 | `/data/gitea/gitea-backup-pre-v126.zip` | 升级前 dump |
|
||
| 旧容器 | 已清理 | 升级验证通过后 `docker rm` |
|
||
| 变更记录 | `~/.openclaw/workspace-jiangwei/changes/gitea-emergency-2026-06-10.md` | 完整操作日志 |
|
||
|
||
| 环境 | 位置 | 说明 |
|
||
|------|------|------|
|
||
| Mac mini 本机 | `~/.sanguo_projects/<project>/` | 主力开发和运行环境 |
|
||
| NAS Docker | `192.168.2.154` | 部分服务(Gitea、回测等) |
|
||
|
||
#### 2.4.7 Gitea 迁移验证记录(2026-06-11 司马懿)
|
||
|
||
> 验证 Gitea 从 gitee 迁移完成后的状态。所有验证在 2026-06-11 完成。
|
||
|
||
**仓库迁移状态**:
|
||
|
||
| 项目 | Gitea 仓库 | 开发目录 | 远程地址 | gitee 残留 |
|
||
|------|-----------|---------|---------|----------|
|
||
| sanguo_moziplus_v2 | `sanguo/sanguo_moziplus_v2` | `~/.openclaw/sanguo_projects/sanguo_moziplus_v2/` | `http://192.168.2.154:3000/sanguo/sanguo_moziplus_v2.git` | ✅ 无 |
|
||
| sanguo_quant_live | `sanguo/sanguo_quant_live` | `~/.openclaw/sanguo_projects/sanguo_quant_live/` | `http://192.168.2.154:3000/sanguo/sanguo_quant_live.git` | ✅ 无 |
|
||
| sanguo_vnpy | `sanguo/sanguo_vnpy` | `~/.openclaw/sanguo_projects/sanguo_vnpy/` | `http://192.168.2.154:3000/sanguo/sanguo_vnpy.git` | ✅ 无 |
|
||
|
||
**验证方法**:在 3 个开发目录分别执行 `git remote -v`,确认 origin 指向 gitea 且无 gitee remote。
|
||
|
||
**CI 管道验证**:
|
||
|
||
| 验证项 | 结果 | 备注 |
|
||
|--------|------|------|
|
||
| PR #33 Lint 修复 CI 通过 | ✅ | flake8 全通过 |
|
||
| CD pipeline (deploy.yml) 合并 | ✅ | 含 CI + deploy + notify-deploy-failure 三个 job |
|
||
| Branch protection 生效 | ✅ | main 分支需 CI 通过 + 1 人 Review 才能合并 |
|
||
| Gitea squash merge 兼容 | ✅ | `merge_commit_sha` 在 squash merge 下仍等于 gitea.sha |
|
||
|
||
**工具链事件中枢验证**:
|
||
|
||
| Webhook → Mail 流 | 验证结果 |
|
||
|-------------------|--------|
|
||
| PR opened → Review 请求 Mail | ✅ 司马懿收到 PR #30-#35 的 Review 请求 |
|
||
| PR review → 结果 Mail | ✅ 张飞/庞统收到 Review 结果通知 |
|
||
| Issue assigned → 指派 Mail | ✅ (E2E 验证通过) |
|
||
| CI 失败评论 → 通知 Mail | ✅ (E2E 验证通过) |
|
||
| PR synchronize → reviewer 重审 Mail | ✅ 新增(§23) |
|
||
| Review COMMENTED → PR 作者通知 | ✅ 新增(§23) |
|
||
|
||
**Agent Gitea 凭据**(各 Agent 自行持有 PAT):
|
||
|
||
| Agent | Gitea 用户名 | PAT 用途 |
|
||
|-------|-------------|--------:|
|
||
| simayi-challenger | simayi-challenger | PR Review 提交 |
|
||
| pangtong-fujunshi | pangtong-fujunshi | PR 创建/合并、代码 push |
|
||
| jiangwei-infra | jiangwei-infra | 基础设施配置(admin 级操作) |
|
||
|
||
---
|
||
|
||
## §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` 分支:
|
||
- 不允许直接 push(hotfix 除外)
|
||
- PR 必须通过 CI(至少 unit + integration tests)
|
||
- PR 必须至少 1 人 Review(人或 Agent)
|
||
- **hotfix 直接 push main 后必须触发 CI**(至少 lint + unit),CI 失败则自动 revert(M1 修订)
|
||
- 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.26.2 不支持 `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 | 标准 + build(E2E 仅 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 新建)
|
||
├─ 临时 SQLite(tmpdir,跑完销毁)
|
||
├─ 临时端口(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 protection(CI+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 自动触发** | Mail 通知 PR 作者合并完成(PR #38) | 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 Approve,merge 按钮灰色不可点
|
||
- 配置位置:Gitea → 仓库 Settings → Branches → Branch Protection
|
||
|
||
**4. Agent 间通知机制总结**
|
||
|
||
| 场景 | 通知方式 | 为什么不用另一种 |
|
||
|------|---------|----------------|
|
||
| 人→Agent任务指派 | Mail | Gitea Issue 不会主动通知 Agent,需要 Mail 推送 |
|
||
| CI 结果 | Gitea Status Check(PR页面)+ daemon Webhook 转发 Mail(Agent通知) | 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}
|
||
```
|
||
|
||
**模板 2:CI 失败通知(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 场景 B:Bug 修复
|
||
|
||
```
|
||
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-runner(Mac 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-workflow,CI 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 | Mail 通知 PR 作者合并完成(PR #38 恢复) |
|
||
| `issue_comment` | PR/Issue 评论 | 评论者, 内容 | CI workflow 写的失败评论 → 转发 Mail |
|
||
| `issues` (opened+assigned) | Issue 创建/指派 | Issue ID, 标题, 被指派人 | → Mail 通知开发者 |
|
||
| `release` | Release 创建 | tag, 名称 | 触发完整 CI+部署 |
|
||
|
||
> **说明**:Webhook 是单向 POST,Gitea 发完不管。需要在 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"]:
|
||
# PR #38: 通知 PR 作者合并完成
|
||
await _handle_pr_closed(event)
|
||
|
||
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 创建 Issue(priority: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 → 等 CI(CI 会自动跑)
|
||
4. CI 通过后创建 PR(Gitea API: POST /repos/{owner}/{repo}/pulls)
|
||
5. 等 Review(司马懿会自动收到通知)
|
||
6. Review 通过后 merge PR
|
||
|
||
如果 CI 失败:修复后重新 push,回到步骤 3
|
||
如果 Review 不通过:按意见修改后重新 push,回到步骤 3
|
||
|
||
完成后回复此 Mail 确认。
|
||
时限:{deadline}
|
||
```
|
||
|
||
**模板 2:Review 请求(Webhook → 司马懿)**
|
||
|
||
```
|
||
Title: Review 请求: PR #{N}
|
||
Text:
|
||
PR: {url}
|
||
标题: {title}
|
||
风险级别: {level}
|
||
改动文件: {file_list}
|
||
|
||
流程:
|
||
1. 读取 PR diff(Gitea API: GET /repos/{owner}/{repo}/pulls/{N}.diff)
|
||
2. 按审查清单审查(参考 code-review Skill)
|
||
3. 提交 Review(Gitea API: POST /repos/{owner}/{repo}/pulls/{N}/reviews)
|
||
- APPROVE 或 REQUEST_CHANGES
|
||
4. 提交后张飞会自动收到通知
|
||
|
||
完成后回复此 Mail 确认。
|
||
时限:{deadline}
|
||
```
|
||
|
||
**模板 3:Review 结果通知(Webhook → 张飞)**
|
||
|
||
```
|
||
Title: Review {result}: PR #{N}
|
||
Text:
|
||
PR: {url}
|
||
结果: {APPROVED / REQUEST_CHANGES}
|
||
审查意见: {review_body}
|
||
|
||
{如果通过}:
|
||
流程:merge PR(Gitea API: POST /repos/{owner}/{repo}/pulls/{N}/merge)
|
||
|
||
{如果不通过}:
|
||
流程:
|
||
1. 按审查意见修改代码
|
||
2. push → CI 自动跑
|
||
3. CI 通过后等重新 Review
|
||
|
||
完成后回复此 Mail 确认。
|
||
```
|
||
|
||
**模板 4:CI 失败通知(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 用户名(已确认一致)
|
||
|
||
姜维确认(2026-06-07):Gitea 注册用户名就是完整 Agent ID,短名用户已删除。
|
||
|
||
| Agent ID(= Git 用户名) |
|
||
|-------------------------|
|
||
| pangtong-fujunshi |
|
||
| simayi-challenger |
|
||
| zhangfei-dev |
|
||
| guanyu-dev |
|
||
| zhaoyun-data |
|
||
| jiangwei-infra |
|
||
|
||
不需要映射表,`to_agent_id()` 直用。
|
||
|
||
|
||
---
|
||
|
||
## §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 类型 | 中枢产出就是 Mail(from=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 注册用户名就是完整 Agent ID(zhangfei-dev / simayi-challenger 等),短名已删除。`to_agent_id()` 直用,不需要映射表 |
|
||
| 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 创建 Issue → 触发 issues 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 自动关闭~~ |
|
||
| `issues` created (标题匹配 🔴 部署失败) | Deploy workflow → Webhook | 姜维 + 庞统 | `deploy_failure.md` | 仓库、commit、失败原因 |
|
||
|
||
### 2.3 事件过滤规则
|
||
|
||
不是所有事件都需要处理:
|
||
|
||
| 规则 | 说明 |
|
||
|------|------|
|
||
| 只处理白名单内的事件类型 | 未知的忽略 + 日志 |
|
||
| issue_comment 需判断来源 | 只处理 CI workflow 写的评论(按特定前缀匹配:`❌ **CI 失败**` 或统一后的 `[CI]` 前缀) |
|
||
| PR 作者/审查者必须是已知 Agent | 未知的忽略 + 日志 |
|
||
| 幂等:同一事件不重复创建 Mail | 双重去重:① delivery UUID(`{event}-{delivery}`)标准幂等;② review 事件 payload 内容去重(`{event}:{pr_num}:{sender}:{sha256(body_or_content)[:16]}`),防御同一 review 被不同来源重复提交(2026-06-09 新增) |
|
||
|
||
---
|
||
|
||
### §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 diff(Gitea API: GET /repos/{repo}/pulls/{pr_number}.diff)
|
||
2. 按审查清单审查(参考 code-review Skill)
|
||
3. 提交 Review(Gitea 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/config/
|
||
└── agents.py # Agent ID 统一注册表
|
||
|
||
src/api/
|
||
├── toolchain_routes.py # 事件中枢路由(~250行)
|
||
├── 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_failure.md
|
||
```
|
||
|
||
### 4.2 toolchain_routes.py 接口设计
|
||
|
||
```python
|
||
# src/api/toolchain_routes.py
|
||
|
||
import asyncio
|
||
from fastapi import APIRouter, Request, Response
|
||
from src.config.agents import AGENT_IDS
|
||
from src.daemon.toolchain_templates import TemplateEngine
|
||
|
||
router = APIRouter()
|
||
engine = TemplateEngine()
|
||
|
||
GITEA_WEBHOOK_SECRET = os.environ.get("GITEA_WEBHOOK_SECRET", "")
|
||
_idempotency_lock = asyncio.Lock()
|
||
|
||
@router.post("/webhook/gitea")
|
||
async def 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. 签名验证(HMAC-SHA256,Gitea 1.23.4 已确认)
|
||
if GITEA_WEBHOOK_SECRET:
|
||
expected = hmac.new(GITEA_WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
|
||
if not hmac.compare_digest(expected, (x_gitea_signature or "")):
|
||
return Response(status_code=403, content="signature verification failed")
|
||
|
||
event = json.loads(body)
|
||
|
||
# 2. 幂等检查(asyncio.Lock 防并发竞态)
|
||
if x_gitea_event and x_gitea_delivery:
|
||
async with _idempotency_lock:
|
||
if _is_duplicate(x_gitea_event, x_gitea_delivery):
|
||
return Response(status_code=200, content="duplicate")
|
||
|
||
# 3. 路由到对应处理器
|
||
handler = HANDLERS.get(x_gitea_event)
|
||
if not handler:
|
||
return Response(status_code=200, content="ignored")
|
||
|
||
# 4. 处理事件 → 创建 Mail
|
||
try:
|
||
result = await handler(engine, event)
|
||
return Response(status_code=200)
|
||
except Exception as e:
|
||
logger.exception("Failed to handle %s event", x_gitea_event)
|
||
return Response(status_code=500, content=str(e))
|
||
```
|
||
|
||
### 4.3 事件处理器
|
||
|
||
每个事件类型一个处理函数,职责:解析 payload → 选模板 → 填充 → 创建 Mail Task。
|
||
|
||
```python
|
||
async def handle_issues(engine: TemplateEngine, event: dict) -> str:
|
||
"""issues 事件分发:assigned → 通知被指派人;opened + 标题匹配 → 部署失败通知"""
|
||
action = event.get("action")
|
||
|
||
if action == "assigned":
|
||
return await _handle_issue_assigned(engine, event)
|
||
|
||
if action == "opened":
|
||
title = event.get("issue", {}).get("title", "")
|
||
if "部署失败" in title:
|
||
return await _handle_deploy_failure(engine, event)
|
||
|
||
return None # 忽略其他 action
|
||
|
||
|
||
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"],
|
||
"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"],
|
||
}
|
||
|
||
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"]
|
||
|
||
# 匹配 CI workflow 写的失败评论(当前格式: "❌ **CI 失败**",未来统一为 "[CI]")
|
||
if "[CI]" in comment_body or "CI 失败" in comment_body:
|
||
return await handle_ci_comment(engine, event)
|
||
# 部署失败当前走创建 Issue(不走 PR comment),见 §16.8 #6
|
||
return None
|
||
|
||
return None # 非 CI/部署评论不处理
|
||
|
||
|
||
# HANDLERS 注册表
|
||
HANDLERS = {
|
||
"pull_request": handle_pull_request, # 只处理 opened
|
||
"pull_request_review": handle_review_submitted,
|
||
"issues": handle_issues, # 处理 assigned + opened(部署失败)
|
||
"issue_comment": handle_issue_comment, # 只处理 CI 失败评论
|
||
}
|
||
```
|
||
|
||
### 4.4 共用函数
|
||
|
||
```python
|
||
def to_agent_id(gitea_username: str) -> str:
|
||
"""Gitea 用户名 → Agent ID。已确认一致,直用。"""
|
||
return gitea_username
|
||
|
||
def create_mail_task(to: str, title: str, text: str, meta: dict) -> str:
|
||
"""创建 Mail Task(from=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 提取改动文件列表
|
||
payload 只有 changed_files 数量,需要额外调用 Gitea API:
|
||
GET /repos/{owner}/{repo}/pulls/{number}/files
|
||
"""
|
||
...
|
||
```
|
||
|
||
### 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 |
|
||
| PR 文件获取失败(3次重试后) | 降级为 risk_level=standard + 错误信息写入 Mail 正文 | 200(handler 内部处理) |
|
||
| 模板填充失败 | 日志 error | 500(触发 Gitea 重试) |
|
||
| Mail 创建失败 | 日志 error | 500(触发 Gitea 重试) |
|
||
|
||
**原则**:
|
||
- daemon 自身能处理的错误(格式、过滤、幂等)→ 返回 200,不让 Gitea 无意义重试
|
||
- daemon 处理不了的错误(数据库、内部异常)→ 返回 500,让 Gitea 重试
|
||
|
||
---
|
||
|
||
### §16.6. CI/Deploy Workflow 配合
|
||
|
||
### 6.1 CI 失败通知
|
||
|
||
CI workflow 已有 `notify-on-failure` job(ci.yml),当前格式:
|
||
```
|
||
❌ **CI 失败**
|
||
|
||
请检查 CI 日志并修复。
|
||
|
||
触发 commit: `abc1234`
|
||
```
|
||
|
||
**需要修改**:将 comment 前缀统一为 `[CI]`,方便中枢匹配:
|
||
```yaml
|
||
# .gitea/workflows/ci.yml — notify-on-failure job
|
||
-d "{\"body\": \"❌ **CI 失败**\\n\\n请检查 CI 日志并修复。\"}"
|
||
+d "{\"body\": \"[CI] 失败\\n\\n分支: ${{ gitea.ref_name }}\\n触发 commit: \\`${{ gitea.sha }}\\`\\n请检查日志并修复。\"}"
|
||
```
|
||
|
||
### 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),直用,不需要映射表 |
|
||
| 2 | Gitea Webhook secret | ✅ 已配置 | 组织级 webhook secret = `22760993dff898a190731da43aa8d964`,daemon `GITEA_WEBHOOK_SECRET` 同步 |
|
||
| 3 | CI workflow PR comment | ✅ 已有 | ci.yml 已有 notify-on-failure job 写 PR comment,格式为 `❌ **CI 失败**...`,需改为 `[CI]` 前缀 |
|
||
| 4 | `from=system` 走内部函数 | ✅ 已确定 | 走内部函数(和 mail_notify.py 一致),不走 HTTP API |
|
||
| 5 | PR changed_files | ✅ 已实现 | `_fetch_pr_files()` 调用 Gitea API,3 次重试 + 失败信息写入 Mail正文 |
|
||
| 6 | Deploy workflow 通知方式 | ✅ 已确认 | 当前创建 Issue(非 PR comment)。部署通知走 `issues` Webhook(issue created)而非 `issue_comment` |
|
||
| 7 | 签名算法 | ✅ 已确认 | Gitea 使用 HMAC-SHA256,代码注释已补 |
|
||
| 8 | Webhook 作用范围 | ✅ 组织级 | Gitea 组织级 webhook(Hook ID=28),覆盖 sanguo 下所有仓库,新增仓库自动覆盖 |
|
||
| 9 | ALLOWED_HOST_LIST | ✅ 已修复 | Gitea 容器配置 `192.168.2.153, 127.0.0.1, localhost, 172.17.0.0/16, 192.168.2.0/24` |
|
||
| 10 | Gitea review payload 格式 | ✅ 姜维调研确认(2026-06-08) | Gitea v1.23.4 review payload 只有 `type` + `content`,没有 `state`/`body`/`user`,这不是 org vs repo 差异而是 Gitea 设计。v1.24.0 格式不变。双格式兼容是防御性编码,保持现状 |
|
||
| 11 | Spawner compact 检测窗口 | ✅ 已修复 | 窗口 300s→900s,尾部读取 50KB→1MB。实测长对话中 compact 记录被推出窗口导致漏检 |
|
||
| 12 | inform 类型 Mail crash 误标 done | ✅ 已修复 | `_mail_auto_complete` 增加 outcome 感知,inform 用白名单(completed/claimed/no_reply)控制 done 标记。spawner crash cooldown 300s→60s |
|
||
|
||
---
|
||
|
||
### §16.9. 实施计划
|
||
|
||
| 步骤 | 内容 | 依赖 |
|
||
|------|------|------|
|
||
| 1 | `toolchain_routes.py` 骨架 + Gitea Webhook 接收 + 签名验证 | 无 |
|
||
| 2 | `toolchain_templates.py` 模板引擎 | 无 |
|
||
| 3 | 5 个模板文件 | 步骤 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` | ~170(5个文件) | 新增 |
|
||
| `src/api/mail_routes.py` | ~20 | 修改(提取共用函数) |
|
||
| `.gitea/workflows/ci.yml` | ~15 | 修改(加 comment step) |
|
||
| `.gitea/workflows/deploy.yml` | ~15 | 修改(加 comment step) |
|
||
| **总计** | **~480** | |
|
||
|
||
---
|
||
|
||
### §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 的落地细节 |
|
||
|
||
### v2.1 → v2.2 变更(事件中枢完善 + E2E 验证通过)
|
||
|
||
| 编号 | 变更内容 |
|
||
|------|---------|
|
||
| §16.8 | #2 更新:组织级 webhook 已配置(Hook ID=28)+ secret 生效;#5 更新:PR 文件获取已实现 3 次重试;新增 #8(组织级覆盖)和 #9(ALLOWED_HOST_LIST 修复) |
|
||
| §16.4 | 技术设计更新:幂等检查加 `asyncio.Lock` 防并发竞态(T-02);Agent ID 提取到 `config/agents.py` 统一管理(T-04);`_fetch_pr_files` 返回 `Tuple[List[str], str]`,3 次重试 + 失败信息写入 Mail(T-05) |
|
||
| §16.5 | 错误处理更新:PR 文件获取失败不再静默,3 次重试后错误信息写入 Mail 正文 |
|
||
| 全局 | 广播风暴 bug 修复:`mail_notify.py` 校验 Agent ID,非有效 Agent(如 system)路由给庞统 |
|
||
| E2E | S22 模拟测试 13/13 通过;S23 真实 Gitea Webhook 8/8 通过;组织级 webhook 跨仓库投递 + 签名验证通过 |
|
||
|
||
### v2.2 → v3.0 变更(合并 §14 工具链 Skill + 自动部署)
|
||
|
||
| 编号 | 变更内容 |
|
||
|------|----------|
|
||
| §17 | 新增:原 `14-toolchain-skill-and-deploy.md` v1.1 全文合并为 §17「工具链落地设计」 |
|
||
| §17.1 | 目标:L1 TOOLS.md + L2 Skill 升级 + deploy.sh + 端到端验证 |
|
||
| §17.2 | 知识体系四层定位(L1 操作手段 + L2 流程规范) |
|
||
| §17.3 | L1 TOOLS.md:按 6 个角色定制 Gitea API 操作模板 |
|
||
| §17.4 | L2 Skill 升级:7 个现有 Skill 对齐事件中枢 + CI |
|
||
| §17.5 | deploy.sh + deploy.yml 统一接口(rsync + pm2 + health check + rollback) |
|
||
| §17.6 | 端到端验证:S1-S6 六个场景 |
|
||
| §17.7 | 覆盖率渐进策略(P1 只报告 → P2 40% → P3 60%) |
|
||
| §17.8 | 前端展示:Gitea 自带 CI 管理界面 |
|
||
| §17.9 | 实施路线(P1→P4) |
|
||
| §17.10 | 前置条件 Checklist(6 项) |
|
||
| §17.11 | 评审记录(M1/M2 修正 + S1-S4 采纳) |
|
||
|
||
---
|
||
|
||
## §17. 工具链落地设计(原 §14 合并)
|
||
|
||
> **来源**: 原 `14-toolchain-skill-and-deploy.md` v1.1(仲达评审 M1/M2 修正 + S1-S4 采纳)
|
||
> **合并日期**: 2026-06-08
|
||
> **定位**: 工具链从"设计完成"到"正式投入使用"的落地设计
|
||
|
||
---
|
||
|
||
### §17.1. 目标
|
||
|
||
把工具链从设计态推进到可使用态:
|
||
1. **L1 TOOLS.md**:每个 Agent 的 TOOLS.md 加入 Gitea API 操作模板,收到 Mail 后开箱即用
|
||
2. **L2 Skill 升级**:7 个现有 Skill 对齐事件中枢 + CI 实际运行
|
||
3. **deploy.sh + deploy.yml**:补完自动部署的实际脚本
|
||
4. **端到端验证**:用 sanguo/moziplus-v2 实验项目验证全链路
|
||
|
||
---
|
||
|
||
### §17.2. 知识体系四层定位
|
||
|
||
> 来源: `architecture-v3.0.md` §10 BootstrapBuilder
|
||
|
||
| 层 | 定位 | 载体 | Token | 工具链职责 |
|
||
|----|------|------|-------|-----------|
|
||
| **L0** | 铁律层 | AGENTS.md / MEMORY.md | ~500 | 不涉及 |
|
||
| **L1** | 角色层 | SOUL.md + **TOOLS.md** | ~2000 | **Gitea API 操作模板**(Agent 自带,开箱即用) |
|
||
| **L2** | 引擎注入 | BootstrapBuilder 注入 Skill 全文 | ~1500 | **7 个 Skill v2**(流程规范,Daemon 确定性注入) |
|
||
| **L3** | 被动参考 | Skill description,Agent 按需加载 | 按需 | 复用 L2 Skill,不新建 |
|
||
|
||
**核心原则**:
|
||
- L1 给操作手段("怎么做"):curl 命令模板、参数说明
|
||
- L2 给流程规范("什么时候做什么"):审查清单、分支规范、测试策略
|
||
- L3 是 L2 的被动触发版本,不需要单独维护
|
||
|
||
---
|
||
|
||
### §17.3. L1:TOOLS.md Gitea 操作模板
|
||
|
||
#### §17.3.1 设计原则
|
||
|
||
1. **开箱即用**:收到 Mail 后直接复制粘贴 curl 命令即可执行,不需要查文档
|
||
2. **按角色定制**:不是每个 Agent 都需要全套 API,只给该角色需要的
|
||
3. **统一格式**:所有模板使用相同的环境变量约定
|
||
|
||
#### §17.3.2 公共变量约定
|
||
|
||
每个 Agent 的 TOOLS.md 头部加入:
|
||
|
||
```markdown
|
||
## Gitea 工具链
|
||
- **地址**: http://192.168.2.154:3000
|
||
- **组织**: sanguo
|
||
- **认证**: `Authorization: token $GITEA_TOKEN`(各 Agent 使用自己的 token)
|
||
- **CI 管理界面**: http://192.168.2.154:3000/sanguo/{repo}/actions
|
||
```
|
||
|
||
#### §17.3.3 按角色模板
|
||
|
||
##### A. 开发者(张飞/关羽/赵云)— PR 创建 + Merge
|
||
|
||
```markdown
|
||
### 创建 PR
|
||
```bash
|
||
curl -X POST "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls" \
|
||
-H "Authorization: token $GITEA_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"head": "{branch}",
|
||
"base": "main",
|
||
"title": "{标题}"
|
||
}'
|
||
# 返回 .number 即 PR 号
|
||
```
|
||
|
||
### 查看 PR diff
|
||
```bash
|
||
curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls/{pr_number}.diff" \
|
||
-H "Authorization: token $GITEA_TOKEN"
|
||
```
|
||
|
||
### Merge PR(Review 通过后)
|
||
```bash
|
||
curl -X POST "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls/{pr_number}/merge" \
|
||
-H "Authorization: token $GITEA_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"Do": "merge", "merge_title_field": "Merge PR #{pr_number}"}'
|
||
```
|
||
|
||
### 查看 CI 状态
|
||
```bash
|
||
curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/commits/{sha}/status" \
|
||
-H "Authorization: token $GITEA_TOKEN"
|
||
# .state = "success" | "pending" | "failure" | "error"
|
||
```
|
||
```
|
||
|
||
##### B. 审查者(司马懿)— Review 操作
|
||
|
||
```markdown
|
||
### 读取 PR diff
|
||
```bash
|
||
curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls/{pr_number}.diff" \
|
||
-H "Authorization: token $GITEA_TOKEN"
|
||
```
|
||
|
||
### 查看 PR 改动文件列表
|
||
```bash
|
||
curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls/{pr_number}/files" \
|
||
-H "Authorization: token $GITEA_TOKEN"
|
||
# 每个文件有 .filename, .additions, .deletions, .changes
|
||
```
|
||
|
||
### 提交 Review
|
||
```bash
|
||
curl -X POST "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls/{pr_number}/reviews" \
|
||
-H "Authorization: token $GITEA_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"body": "{审查意见}",
|
||
"event": "APPROVED"
|
||
}'
|
||
# event 可选: APPROVED | REQUEST_CHANGES | COMMENT
|
||
# ⚠️ 注意:是 APPROVED(过去式),不是 APPROVE
|
||
```
|
||
|
||
### 风险级别判定(自动 + 确认)
|
||
规则见 code-review Skill。改动者不能降级,只能维持或升级。
|
||
```
|
||
|
||
##### C. 协调者(庞统)— 全套管理
|
||
|
||
```markdown
|
||
### 创建 Issue + 指派
|
||
```bash
|
||
curl -X POST "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/issues" \
|
||
-H "Authorization: token $GITEA_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"title": "{标题}",
|
||
"body": "{描述}",
|
||
"assignees": ["{agent_id}"],
|
||
"labels": [1, 2]
|
||
}'
|
||
# labels 需用数字 ID,先 GET /repos/{owner}/{repo}/labels 查询
|
||
```
|
||
|
||
### 查询仓库 Labels
|
||
```bash
|
||
curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/labels" \
|
||
-H "Authorization: token $GITEA_TOKEN"
|
||
```
|
||
|
||
### 查询 PR 列表
|
||
```bash
|
||
curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls?state=open" \
|
||
-H "Authorization: token $GITEA_TOKEN"
|
||
```
|
||
|
||
### 创建 Release
|
||
```bash
|
||
curl -X POST "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/releases" \
|
||
-H "Authorization: token $GITEA_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"tag_name": "v{version}",
|
||
"name": "v{version}",
|
||
"body": "{changelog}",
|
||
"target_commitish": "main"
|
||
}'
|
||
```
|
||
|
||
### 关闭 Issue
|
||
```bash
|
||
curl -X PATCH "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/issues/{issue_number}" \
|
||
-H "Authorization: token $GITEA_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"state": "closed"}'
|
||
```
|
||
```
|
||
|
||
##### D. 平台运维(姜维)— deploy + runner 管理
|
||
|
||
在开发者模板基础上追加:
|
||
|
||
```markdown
|
||
### 触发手动部署(需要 push 到 main 或手动 re-run)
|
||
```bash
|
||
# Re-run 最近一次 workflow
|
||
curl -X POST "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/actions/runs/{run_id}/rerun" \
|
||
-H "Authorization: token $GITEA_TOKEN"
|
||
```
|
||
|
||
### 查看 deploy-history
|
||
```bash
|
||
cat ~/.sanguo_projects/{project}/data/deploy-history.jsonl
|
||
```
|
||
|
||
### deploy.sh 规范
|
||
见 §17.5 deploy.sh 设计。所有项目必须遵循统一接口。
|
||
```
|
||
|
||
#### §17.3.4 实施清单
|
||
|
||
| Agent | TOOLS.md 新增 | 预计行数 |
|
||
|-------|--------------|---------|
|
||
| zhangfei-dev | 开发者模板(PR/Merge/CI状态) | ~30 行 |
|
||
| guanyu-dev | 开发者模板 | ~30 行 |
|
||
| zhaoyun-data | 开发者模板 | ~30 行 |
|
||
| simayi-challenger | 审查者模板(diff/review/风险判定) | ~40 行 |
|
||
| pangtong-fujunshi | 协调者模板(全套管理) | ~50 行 |
|
||
| jiangwei-infra | 开发者 + 平台运维模板 | ~45 行 |
|
||
|
||
---
|
||
|
||
### §17.4. L2:Skill 升级到 v2
|
||
|
||
#### §17.4.1 升级原则
|
||
|
||
1. **不重写已有内容**,在现有 Skill 基础上追加/修改
|
||
2. **对齐事件中枢**:每个 Skill 说明在事件中枢链路中的位置
|
||
3. **对齐 CI 实际运行**:ci.yml/deploy.yml 已有实际配置,Skill 要反映真实情况
|
||
4. **对齐 Gitea v1.23.4 限制**:不支持 failure()、concurrency、permissions 等
|
||
|
||
#### §17.4.2 升级内容
|
||
|
||
##### git-workflow(小改动)
|
||
|
||
| 改动 | 说明 |
|
||
|------|------|
|
||
| 追加"事件中枢集成"节 | PR 创建 → Gitea Webhook → 中枢通知司马懿 → Review → 中枢通知作者 → Merge → deploy.yml 自动触发 |
|
||
| 追加"CI 自动触发"说明 | push 非 main 分支自动触发 ci.yml;push main 自动触发 deploy.yml |
|
||
| 追加"分支感知"强化 | Agent spawn 后必须 `git branch --show-current`,确认分支正确 |
|
||
|
||
##### code-review(中等改动)
|
||
|
||
| 改动 | 说明 |
|
||
|------|------|
|
||
| 追加"事件中枢触发"节 | 收到 Mail → 读 PR diff → 审查 → 提交 Review → Webhook 自动通知作者 |
|
||
| 更新风险判定规则 | 对齐 §6.1 实际规则(按文件路径自动判定 + 只升不降) |
|
||
| 追加 Gitea Review API 操作 | curl 模板(和 L1 TOOLS.md 一致,这里放流程说明) |
|
||
| 追加审查结论格式 | APPROVE / REQUEST_CHANGES 的标准格式 |
|
||
|
||
##### testing-workflow(小改动)
|
||
|
||
| 改动 | 说明 |
|
||
|------|------|
|
||
| 追加"CI 集成"节 | UT 在 CI 自动跑(ci.yml test job)、coverage 在 deploy.yml ci job 跑 |
|
||
| 追加"E2E 触发方式" | 通过 e2e.yml 手动触发或 `RUN_INTEGRATION=1 pytest` 本地跑 |
|
||
| 追加"测试数据隔离" | CI 使用临时 venv + 临时 SQLite + 临时端口 |
|
||
| 追加"广播风暴禁止" | **禁止在 daemon 运行时跑含创建项目/Task/Mail 的测试**,否则会触发 Agent spawn 导致广播风暴。E2E 测试必须在 CI 隔离环境或 daemon 停止后跑 |
|
||
|
||
##### bugfix-workflow(小改动)
|
||
|
||
| 改动 | 说明 |
|
||
|------|------|
|
||
| 追加"事件中枢链路"节 | Bug Issue 创建/指派 → 中枢发 Mail → 修复 → PR → CI → Review → merge |
|
||
| 追加"CI 验证"步骤 | 修复后必须等 CI 通过再创建 PR |
|
||
|
||
##### hotfix-workflow(小改动)
|
||
|
||
| 改动 | 说明 |
|
||
|------|------|
|
||
| 追加"CI 自动跑"说明 | hotfix push main → deploy.yml 自动跑 CI + 部署 |
|
||
| 追加"失败自动创建 Issue" | deploy.yml notify-on-failure 已实现 |
|
||
| 更新 24h 复盘流程 | 复盘结论写到 Issue 评论中 |
|
||
|
||
##### ci-cd-ops(重写)
|
||
|
||
| 改动 | 说明 |
|
||
|------|------|
|
||
| 重写为 v2 | 对齐实际 ci.yml/deploy.yml 结构 |
|
||
| 新增 Gitea v1.23.4 限制清单 | 不支持 failure()/concurrency/permissions 等,workaround 方案 |
|
||
| 新增覆盖率渐进策略 | P1 只报告 → P2 40% 阈值 → P3 60% 阈值 |
|
||
| 新增 deploy.sh 规范 | 统一接口:--version / --source / --target / --health-check / --rollback |
|
||
| 新增 CI secret 配置 | CI_TOKEN 作为 repository secret 配置 |
|
||
|
||
##### release-workflow(中等改动)
|
||
|
||
| 改动 | 说明 |
|
||
|------|------|
|
||
| 追加"自动部署触发" | tag 创建 → deploy.yml 自动触发 |
|
||
| 追加 deploy-history.jsonl 规范 | 每次 deploy 记录 tag + commit + 时间戳 |
|
||
| 更新 schema 变更规范 | 向前兼容 Checklist(加列不加删、默认值、迁移脚本) |
|
||
|
||
#### §17.4.3 不新建 Skill
|
||
|
||
现有 7 个 Skill 覆盖所有工具链流程。不需要为事件中枢、CI 操作新建 Skill——这些流程固化在 Mail 模板(§16.4.3)中,Skill 只提供共识功能。
|
||
|
||
#### §17.4.4 实施清单
|
||
|
||
| Skill | 改动级别 | 预计改动行数 |
|
||
|-------|---------|------------|
|
||
| git-workflow | 小 | +15 行 |
|
||
| code-review | 中 | +40 行 |
|
||
| testing-workflow | 小 | +25 行(含广播风暴禁止约束) |
|
||
| bugfix-workflow | 小 | +15 行 |
|
||
| hotfix-workflow | 小 | +15 行 |
|
||
| ci-cd-ops | 重写 | ~120 行(原 114 行) |
|
||
| release-workflow | 中 | +30 行 |
|
||
|
||
---
|
||
|
||
### §17.5. deploy.sh + deploy.yml 补完
|
||
|
||
#### §17.5.1 deploy.sh 统一接口
|
||
|
||
每个项目的 `scripts/deploy.sh` 必须遵循以下接口:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# scripts/deploy.sh — 项目部署脚本
|
||
# 用法:
|
||
# bash scripts/deploy.sh --version # 显示当前版本
|
||
# bash scripts/deploy.sh --source=DIR --target=DIR --health-check # 部署
|
||
# bash scripts/deploy.sh --rollback # 回滚到上一版本
|
||
|
||
set -euo pipefail
|
||
|
||
SOURCE_DIR=""
|
||
TARGET_DIR=""
|
||
HEALTH_CHECK=false
|
||
ACTION="deploy"
|
||
|
||
for arg in "$@"; do
|
||
case $arg in
|
||
--version) ACTION="version" ;;
|
||
--source=*) SOURCE_DIR="${arg#*=}" ;;
|
||
--target=*) TARGET_DIR="${arg#*=}" ;;
|
||
--health-check) HEALTH_CHECK=true ;;
|
||
--rollback) ACTION="rollback" ;;
|
||
esac
|
||
done
|
||
|
||
PROJECT_NAME="{project_name}"
|
||
DEPLOY_HISTORY="${TARGET_DIR}/data/deploy-history.jsonl"
|
||
|
||
version() {
|
||
echo "${PROJECT_NAME} deploy version: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||
}
|
||
|
||
deploy() {
|
||
echo "=== Deploying ${PROJECT_NAME} ==="
|
||
echo "Source: ${SOURCE_DIR}"
|
||
echo "Target: ${TARGET_DIR}"
|
||
|
||
# 1. 同步文件(排除不需要部署的)
|
||
# ⚠️ --delete 会删除目标中源没有的文件,必须排除 data/(生产数据)
|
||
rsync -av --delete \
|
||
--exclude='.git' \
|
||
--exclude='__pycache__' \
|
||
--exclude='.venv' \
|
||
--exclude='data' \
|
||
--exclude='tests' \
|
||
--exclude='docs' \
|
||
--exclude='.gitea' \
|
||
--exclude='node_modules' \
|
||
"${SOURCE_DIR}/" "${TARGET_DIR}/"
|
||
|
||
# 2. 安装依赖
|
||
if [ -f "${TARGET_DIR}/pyproject.toml" ]; then
|
||
cd "${TARGET_DIR}"
|
||
python3 -m venv .venv
|
||
.venv/bin/pip install --quiet -e ".[dev]" 2>/dev/null || \
|
||
.venv/bin/pip install --quiet -e . 2>/dev/null || true
|
||
fi
|
||
|
||
# 3. 重启服务
|
||
if command -v pm2 &>/dev/null; then
|
||
pm2 restart ${PROJECT_NAME} 2>/dev/null || true
|
||
fi
|
||
|
||
# 4. 健康检查
|
||
if [ "$HEALTH_CHECK" = true ]; then
|
||
sleep 3
|
||
curl -sf http://localhost:8083/api/health && echo " ✓" || {
|
||
echo " ✗ Health check failed!"
|
||
exit 1
|
||
}
|
||
fi
|
||
|
||
# 5. 记录版本(rollback 时可通过 DEPLOY_OVERRIDE_COMMIT 覆盖)
|
||
local commit_hash
|
||
if [ -n "${DEPLOY_OVERRIDE_COMMIT:-}" ]; then
|
||
commit_hash="${DEPLOY_OVERRIDE_COMMIT}"
|
||
else
|
||
commit_hash=$(cd "${SOURCE_DIR}" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||
fi
|
||
local timestamp
|
||
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
echo "{\"timestamp\": \"${timestamp}\", \"commit\": \"${commit_hash}\", \"source\": \"${SOURCE_DIR}\"}" >> "${DEPLOY_HISTORY}"
|
||
|
||
# 保留最近 10 条
|
||
tail -10 "${DEPLOY_HISTORY}" > "${DEPLOY_HISTORY}.tmp" && mv "${DEPLOY_HISTORY}.tmp" "${DEPLOY_HISTORY}"
|
||
|
||
echo "=== Deploy complete: ${commit_hash} at ${timestamp} ==="
|
||
}
|
||
|
||
rollback() {
|
||
if [ ! -f "${DEPLOY_HISTORY}" ]; then
|
||
echo "No deploy history, cannot rollback"
|
||
exit 1
|
||
fi
|
||
|
||
# 读取倒数第二行的 commit(不依赖 SOURCE_DIR 的 git 状态)
|
||
local prev_line
|
||
prev_line=$(tail -2 "${DEPLOY_HISTORY}" | head -1)
|
||
local prev_commit
|
||
local prev_source
|
||
prev_commit=$(echo "${prev_line}" | python3 -c "import sys,json; print(json.load(sys.stdin)['commit'])" 2>/dev/null)
|
||
prev_source=$(echo "${prev_line}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('source',''))" 2>/dev/null)
|
||
|
||
if [ -z "${prev_commit}" ] || [ "${prev_commit}" = "unknown" ]; then
|
||
echo "Cannot determine previous version"
|
||
exit 1
|
||
fi
|
||
|
||
echo "=== Rolling back to ${prev_commit} ==="
|
||
# checkout 到指定 commit,部署,然后回到 main
|
||
cd "${SOURCE_DIR}"
|
||
local current_branch
|
||
current_branch=$(git branch --show-current)
|
||
git checkout "${prev_commit}"
|
||
# 部署时明确传 commit hash,不依赖 HEAD
|
||
DEPLOY_OVERRIDE_COMMIT="${prev_commit}" deploy
|
||
git checkout "${current_branch:-main}"
|
||
}
|
||
|
||
case $ACTION in
|
||
version) version ;;
|
||
deploy) deploy ;;
|
||
rollback) rollback ;;
|
||
esac
|
||
```
|
||
|
||
#### §17.5.2 deploy.yml 更新
|
||
|
||
替换当前 placeholder:
|
||
|
||
```yaml
|
||
deploy:
|
||
runs-on: macos-arm64
|
||
needs: ci
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Deploy
|
||
env:
|
||
CI_TOKEN: ${{ secrets.CI_TOKEN }}
|
||
run: |
|
||
bash scripts/deploy.sh --source="$GITHUB_WORKSPACE" \
|
||
--target="$HOME/.sanguo_projects/sanguo_moziplus_v2" \
|
||
--health-check
|
||
|
||
- name: Rollback on failure
|
||
if: always()
|
||
env:
|
||
CI_TOKEN: ${{ secrets.CI_TOKEN }}
|
||
run: |
|
||
STATUS=$(curl -sf \
|
||
-H "Authorization: token $CI_TOKEN" \
|
||
"${{ gitea.api_url }}/repos/${{ gitea.repository }}/commits/${{ gitea.sha }}/status" \
|
||
| python3 -c "import sys,json; print(json.load(sys.stdin).get('state',''))" 2>/dev/null || echo "")
|
||
if [ "$STATUS" != "success" ]; then
|
||
echo "Deploy failed, rolling back..."
|
||
bash scripts/deploy.sh --rollback || echo "Rollback failed, manual intervention needed"
|
||
fi
|
||
```
|
||
|
||
#### §17.5.3 实施清单
|
||
|
||
| # | 内容 | 文件 | 说明 |
|
||
|---|------|------|------|
|
||
| D1 | 创建 deploy.sh | moziplus-v2 `scripts/deploy.sh` | 从模板创建,项目名填 sanguo_moziplus_v2 |
|
||
| D2 | 更新 deploy.yml | moziplus-v2 `.gitea/workflows/deploy.yml` | 替换 placeholder |
|
||
| D3 | 同步到 Gitea | push 到 moziplus-v2 | 触发 CI 验证 |
|
||
|
||
---
|
||
|
||
### §17.6. 端到端验证:sanguo/moziplus-v2 实验项目
|
||
|
||
#### §17.6.1 为什么用实验项目
|
||
|
||
- moziplus-v2 主项目已有正式数据(黑板项目、Mail、Task),不适合做破坏性验证
|
||
- sanguo/moziplus-v2 当前是空项目(只有 README.md + .gitea/workflows),可以随意实验
|
||
|
||
#### §17.6.2 验证场景
|
||
|
||
| 场景 | 操作 | 预期结果 |
|
||
|------|------|---------|
|
||
| **S1: CI 触发** | push 分支到实验项目 | ci.yml 自动跑 lint + test |
|
||
| **S2: PR Review 流程** | 创建 PR → 中枢 Mail → 司马懿 Review → 中枢通知作者 | 全链路 Mail 通知 |
|
||
| **S3: CI 失败通知** | push 含 lint 错误的代码 | ci.yml 失败 → 写 PR 评论 → 中枢发 Mail |
|
||
| **S4: 部署流程** | merge PR → push main | deploy.yml 自动跑 + 部署 + 健康检查 |
|
||
| **S5: Issue 指派** | 创建 Issue 并指派 | 中枢发 Mail 给被指派人 |
|
||
| **S6: 幂等验证** | 重复触发同一 delivery_id Webhook | 第二次返回 200 duplicate,不创建重复 Mail |
|
||
|
||
#### §17.6.4 P3 端到端验证结果(2026-06-08)
|
||
|
||
| 场景 | 结果 | 说明 |
|
||
|------|------|------|
|
||
| **S1: CI 触发** | ✅ | push test/p3-e2e-s1 → ci.yml 触发。首次因 act-runner 离线未触发,姜维修复后积压任务自动执行 |
|
||
| **S2: PR Review 流程** | ✅ | PR opened → 中枢发 Review 请求 Mail 给 simayi-challenger ✅。Review APPROVED → 中枢发 Mail 给 PR 作者 ✅(Gitea 1.23.4 支持此 webhook) |
|
||
| **S3: CI 失败通知** | ✅ | [CI] 评论 → 中枢发 Mail 给 PR作者 ✅ |
|
||
| **S4: 部署流程** | ⏭️ 跳过 | 依赖 CI 先跑通,未单独验证 |
|
||
| **S5: Issue 指派** | ✅ | Issue 指派 zhangfei-dev → 收到 Mail ✅ |
|
||
| **S6: 幂等验证** | ✅ | 同 delivery_id 第二次返回 `duplicate`,不创建重复 Mail ✅ |
|
||
|
||
##### 调研发现
|
||
|
||
**发现 1:Gitea Review API event 枚举值**
|
||
|
||
Gitea ReviewStateType 枚举值应为 `APPROVED`(不是 `APPROVE`)。使用错误的枚举值会创建 PENDING review,且 PENDING 不触发 webhook 通知。
|
||
|
||
- 正确:`{"event": "APPROVED"}`
|
||
- 错误:`{"event": "APPROVE"}` → 创建 PENDING review,webhook 不触发
|
||
|
||
已修正:TOOLS.md 模板 + code-review Skill 中的 event 值。
|
||
|
||
**发现 2:Gitea 1.23.4 支持 PullRequestReview webhook**
|
||
|
||
之前误判为"不支持",实际原因同发现 1——错误的枚举值创建了 PENDING review,PENDING 不在 webhook switch 分支中导致报 "Unsupported review webhook type"。使用正确的 APPROVED event 后 webhook 正常触发。
|
||
|
||
daemon webhook handler 已正确覆盖:
|
||
- `pull_request_review` 事件 → `_handle_pull_request_review()`
|
||
- 只处理 APPROVED / REQUEST_CHANGES,跳过 COMMENTED 和 PENDING
|
||
- 通知对象:PR 作者
|
||
|
||
**发现 3:act-runner 进程管理**
|
||
|
||
act-runner 未纳入进程管理,崩溃后不会自恢复。姜维已修复:
|
||
- 纳入 PM2 托管(进程名:sanguo-act-runner,id=44)
|
||
- 启动命令:`pm2 start ~/bin/act_runner --name sanguo-act-runner --cwd ~/.config/act-runner/ -- daemon --config ~/.config/act-runner/config.yaml`
|
||
- 崩溃自动重启 ✅
|
||
- 开机自启:依赖 pm2 startup(需 sudo 配置 launchd)
|
||
|
||
#### §17.6.3 实验项目 CI 配置
|
||
|
||
从 moziplus-v2 的 ci.yml 精简,实验项目只需要基本验证:
|
||
|
||
```yaml
|
||
name: CI
|
||
on:
|
||
push:
|
||
branches: ['**', '!main']
|
||
pull_request:
|
||
types: [opened, synchronize]
|
||
|
||
jobs:
|
||
lint:
|
||
runs-on: macos-arm64
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- run: echo "lint placeholder"
|
||
|
||
test:
|
||
runs-on: macos-arm64
|
||
needs: lint
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- run: echo "test placeholder"
|
||
```
|
||
|
||
> **注意**:主项目 ci.yml/deploy.yml 的 runs-on 已从 `ubuntu-latest` 改为 `macos-arm64`,对齐 act-runner 实际 label。deploy.yml 的 placeholder 也已替换为实际 deploy.sh 命令。
|
||
|
||
---
|
||
|
||
### §17.7. 覆盖率渐进策略
|
||
|
||
#### §17.7.1 P1 阶段(启用后 2 周)— 只报告不阻断
|
||
|
||
**零改动**。当前 deploy.yml 的 ci job 已包含 `--cov=src --cov-report=term-missing`(push main 时自动跑)。
|
||
|
||
注意:ci.yml(非 main 分支)没有 coverage 配置——快速门控不需要覆盖率,这是有意为之。
|
||
|
||
收集基线数据后决定 P2 阈值。
|
||
|
||
#### §17.7.2 后续阶段(仅设计,暂不实施)
|
||
|
||
| 阶段 | 时间 | 策略 | 触发方式 |
|
||
|------|------|------|---------|
|
||
| P2 | 启用后 1 月 | 40% 阈值,低于警告不阻断 | ci.yml 加 coverage threshold check |
|
||
| P3 | 启用后 2 月+ | 60% 阈值,低于阻断 | ci.yml 加 `exit 1` |
|
||
|
||
---
|
||
|
||
### §17.8. 前端展示
|
||
|
||
#### §17.8.1 Gitea 自带管理界面
|
||
|
||
Gitea v1.23.4 自带完整的 CI 管理界面:
|
||
|
||
| 功能 | URL |
|
||
|------|-----|
|
||
| CI Runs 列表 | `http://192.168.2.154:3000/sanguo/{repo}/actions` |
|
||
| 单次 Run 日志 | `http://192.168.2.154:3000/sanguo/{repo}/actions/runs/{id}` |
|
||
| PR CI Status | PR 页面自动显示 CI 状态徽章 |
|
||
| Webhook 管理 | 仓库 Settings → Webhooks |
|
||
|
||
**不需要自己做 CI 前端。**
|
||
|
||
#### §17.8.2 moziplus v2 前端展示
|
||
|
||
工具链事件的 Mail 通知已在 moziplus v2 前端展示(Mail 列表页)。
|
||
|
||
如果未来要加,唯一值得做的是:在 moziplus 前端加一个「工具链状态」面板,聚合展示各仓库最近 CI 状态。这是 P4,不阻塞使用。
|
||
|
||
---
|
||
|
||
### §17.9. 实施路线
|
||
|
||
| 优先级 | 内容 | 耗时 | 前置 |
|
||
|--------|------|------|------|
|
||
| **P1** | L1 TOOLS.md(6 个 Agent) | 2h | 无 |
|
||
| **P1** | L2 Skill 升级(7 个) | 1d | 无 |
|
||
| **P2** | deploy.sh + deploy.yml | 2h | P1 Skill 升级(ci-cd-ops 定义了 deploy.sh 规范) |
|
||
| **P3** | sanguo/moziplus-v2 端到端验证 | 2h | P1 + P2 |
|
||
| **P4** | 前端工具链状态面板 | 按需 | 不阻塞 |
|
||
|
||
---
|
||
|
||
### §17.10. 前置条件 Checklist
|
||
|
||
工具链投入使用前必须确认:
|
||
|
||
| # | 条件 | 状态 | 谁确认 |
|
||
|---|------|------|--------|
|
||
| 1 | act-runner 已注册且 label = `macos-arm64` | ✅ PM2 托管(sanguo-act-runner, id=44),崩溃自动重启 | 姜维确认 |
|
||
| 2 | Gitea repository secrets 已配置(CI_TOKEN) | ✅ 姜维确认(sanguo/moziplus-v2 已配 CI_TOKEN) | 姜维 |
|
||
| 3 | Gitea 组织级 Webhook 已启用(Hook ID=28) | ✅ 已确认 | 已确认 |
|
||
| 4 | 各 Agent 的 GITEA_TOKEN 环境变量 | ✅ 已写入各 Agent TOOLS.md,姜维确认 token 记录存在 | 庞统+姜维 |
|
||
| 5 | main 分支保护规则(Review 才能 merge) | ✅ 姜维已配置(moziplus-v2 + sanguo_moziplus_v2,需1个approve) | 姜维 |
|
||
| 6 | 禁止在 daemon 运行时跑全量 E2E | ✅ 已警告司马懿 | 已确认 |
|
||
|
||
> 第 5 点很关键——如果 main 分支没有保护规则,开发者可以直接 push main 跳过 Review。
|
||
|
||
> act-runner 已于 2026-06-08 纳入 PM2 托管(姜维),崩溃自动重启 + pm2 save 已保存。开机自启依赖 pm2 startup(需 sudo 配置 launchd),P4 完成。
|
||
|
||
---
|
||
|
||
### §17.11. 评审记录(原 §14 评审)
|
||
|
||
#### v1.0 → v1.1 修订清单(仲达评审)
|
||
|
||
| 编号 | 类型 | 问题 | 修订内容 |
|
||
|------|------|------|----------|
|
||
| M1 | 必须修 | rsync --delete 会删 data/ | §17.5.1 --exclude 加 `data` + `node_modules` |
|
||
| M2 | 必须修 | runs-on 与实际环境不一致 | 实际 ci.yml/deploy.yml 已用 `macos-arm64`,CI 已跑通,属仲达误判。文档 §17.6.3 已明确说明 |
|
||
| S1 | 建议 | rollback commit 获取有竞态 | §17.5.1 rollback 改用 DEPLOY_OVERRIDE_COMMIT 显式传递,不依赖 git HEAD |
|
||
| S2 | 建议 | pip install 缺 lock file | 采纳但 P3,当前单一部署环境风险低 |
|
||
| S3 | 建议 | 缺前置条件 checklist | 新增 §17.10 前置条件 Checklist |
|
||
| S4 | 建议 | 验证场景补 S6 幂等 | §17.6.2 新增 S6 幂等验证场景 |
|
||
| — | 评审 | 庞统模板缺关闭 Issue curl | §17.3.3 C 节补关闭 Issue 模板 |
|
||
| — | 评审 | testing-workflow 加广播风暴禁止 | §17.4.2 testing-workflow 补充约束 |
|
||
| — | 评审 | ci-cd-ops 行数估算措辞 | 修正:重写后预计 ~120 行(原 114 行) |
|
||
| — | 评审 | 覆盖率 P1 前提需确认 | §17.7.1 明确指出 deploy.yml 已有 coverage,ci.yml 有意不加 |
|
||
|
||
---
|
||
|
||
### v3.0 → v3.1 变更(P3 端到端验证 + 调研结论)
|
||
|
||
| 编号 | 变更内容 |
|
||
|------|----------|
|
||
| §17.3.3 | Review API event 枚举值修正:APPROVE → APPROVED |
|
||
| §17.6.3 | 注意更新:runs-on 已改为 macos-arm64,placeholder 已替换 |
|
||
| §17.6.4 | 新增 P3 端到端验证结果(S1-S6 逐项) |
|
||
| §17.6.4 | 新增调研发现:Review API 枚举值、PullRequestReview webhook 支持、act-runner PM2 托管 |
|
||
| §17.10 | #1 状态更新:act-runner 已纳入 PM2 托管 |
|
||
|
||
### v3.1 → v3.2 变更(工具链修复 + Mail 投递 bug 修复)
|
||
|
||
| 编号 | 变更内容 |
|
||
|------|----------|
|
||
| §16.4 | Review handler 双格式兼容:HANDLERS 注册表同时注册 `pull_request_review` / `pull_request_approved` 等多种事件名;`_handle_pull_request_review` 兼容 repo webhook(review.state/body/user)和 org webhook(review.type/content/sender)两种 payload 格式 |
|
||
| §16.8 #10 | Gitea v1.23.4 review payload 调研结论(姜维 2026-06-08):Gitea v1.23.4 review payload 只有 `type` + `content`,没有 `state`/`body`/`user`,这不是 org vs repo 差异而是 Gitea 设计。v1.24.0 格式不变。双格式兼容是防御性编码,保持现状 |
|
||
| §16.8 #11 | Spawner compact 检测窗口修复:窗口 300s→900s,尾部读取 50KB→1MB。实测长对话中 compact 记录被推出窗口导致漏检 |
|
||
| §16.8 #12 | inform 类型 Mail crash 误标 done bug 修复:`_mail_auto_complete` 增加 outcome 感知,inform 用白名单(completed/claimed/no_reply)控制 done 标记。spawner crash cooldown 300s→60s |
|
||
|
||
---
|
||
|
||
### 一、问题诊断
|
||
|
||
#### 1.1 E2E 真实场景测试暴露的三个断层
|
||
|
||
主公在 moziplus-v2 仓库创建了 Issue #32(添加 /api/stats 端点),指派张飞。链条在第一步就断了。
|
||
|
||
| 断层 | 现象 | 根因 |
|
||
|------|------|------|
|
||
| **Agent 不知道该做什么** | 张飞收到 Issue 指派 Mail,回复"已阅"就结束了 | Mail 模板(issue_assigned.md)5 行信息,无流程引导;spawn prompt 说"已阅即可" |
|
||
| **Agent 去错了仓库** | 张飞去读了 sanguo_moziplus_v2 平台代码,而不是空的实验仓库 moziplus-v2 | Mail 模板没有仓库 clone URL,张飞凭习惯去了开发目录 |
|
||
| **Agent 在 Control UI 提问** | 张飞遇到问题直接在 Control UI 问主公,没有去 Gitea Issue 评论 | 没有任何地方引导"有疑问去 Gitea Issue 评论" |
|
||
| **Agent 不知道怎么协作** | 张飞判断任务需要澄清,但不知道该怎么请求澄清 | 没有"做不了→在 Issue 评论 / Mail 庞统"的回退路径 |
|
||
| **跨 Agent @mention 无法通知** | 张飞在 Issue 评论 @赵云,赵云收不到通知 | issue_comment handler 只处理 [CI] 评论,@mention 被忽略 |
|
||
|
||
#### 1.2 根因:工具链在四层架构中的断层
|
||
|
||
| 层 | 应该有 | 实际有 | Gap |
|
||
|---|---|---|---|
|
||
| **L0 铁律** | — | — | 无需改动 |
|
||
| **L1 角色** | 工具链协作行为规范(所有 Agent 共享) | 无 | AGENTS.md 没有工具链相关内容 |
|
||
| **L2 引擎注入** | 事件上下文(仓库 clone URL、Gitea API、Issue/PR 详情) | Mail 模板只有 5 行摘要 | 缺仓库信息和流程引导 |
|
||
| **L3 被动参考** | 技术细节(分支命名、commit 规范、PR 创建方式) | git-workflow 等 Skill 已存在但没人触发 | Agent 不知道该加载哪个 Skill |
|
||
|
||
---
|
||
|
||
### 二、改造方案:四层归属
|
||
|
||
#### 2.1 分层原则
|
||
|
||
| 层 | 放什么 | 不放什么 | 理由 |
|
||
|---|---|---|---|
|
||
| **L0** | 不放 | — | 工具链不是安全底线 |
|
||
| **L1** | 协作行为规范:收到什么通知该做什么、遇到问题怎么办 | 技术细节(分支命名、commit 格式) | 行为规范是团队常识,每个 Agent 都要知道 |
|
||
| **L2** | 事件上下文:仓库 clone URL、Gitea API URL、Issue/PR 链接、动态信息 | 固定的协作流程 | 动态信息每次不同,由 Mail 模板 + spawn 时注入 |
|
||
| **L3** | 技术细节:git-workflow、code-review 等 Skill 全文 | — | 按需加载,Agent 知道"我要提 PR"后自己读 |
|
||
|
||
#### 2.2 各层具体内容
|
||
|
||
##### L1:AGENTS.md 加工具链协作行为段(所有 Agent 统一)
|
||
|
||
```markdown
|
||
## 工具链协作(Gitea)
|
||
|
||
收到 Gitea 事件通知(Issue 指派、Review 请求、CI 失败等)时,按以下流程操作:
|
||
|
||
### 基本流程
|
||
- **Issue 指派** → clone 仓库 → 开分支 → 编码 → 提 PR(参考 git-workflow Skill)
|
||
- **Review 请求** → 读 PR diff(Gitea API)→ 提交 Review(参考 code-review Skill)
|
||
- **Review 通过** → 等 merge
|
||
- **Review 驳回** → 看 review body → 修代码 → 重新 push
|
||
- **CI 失败** → 看错误摘要 → 修代码 → push(自动重触发 CI)
|
||
- **部署失败** → 查 deploy 日志 → 修复
|
||
|
||
### 协作规则
|
||
- **有疑问?** 在 Gitea Issue 下评论,不要在 Control UI 或 Mail 里问
|
||
- **需要别人帮忙?** 在 Issue 评论中 @mention 对应 Agent(如 @zhaoyun-data)
|
||
- **做不了?** 回复 Mail 说明原因和建议的接手人
|
||
- **获取完整上下文** → 用 Gitea API 拉取 Issue 详情和评论,不要只看 Mail 里的快照
|
||
|
||
### Gitea API 速查
|
||
> 其中 `{owner}/{repo}` 替换为实际仓库,如 `sanguo/sanguo_moziplus_v2`
|
||
- Issue 详情: GET /api/v1/repos/{owner}/{repo}/issues/{number}
|
||
- Issue 评论: GET /api/v1/repos/{owner}/{repo}/issues/{number}/comments
|
||
- PR diff: GET /api/v1/repos/{owner}/{repo}/pulls/{number}.diff
|
||
- 提交 Review: POST /api/v1/repos/{owner}/{repo}/pulls/{number}/reviews
|
||
```
|
||
|
||
**改动范围**:6 个 Agent 的 AGENTS.md 各加一段(内容统一)。
|
||
|
||
##### L2:Mail 模板精简 + 事件上下文注入
|
||
|
||
**原则**:模板只放摘要 + 链接 + 仓库信息,不写固定步骤(步骤在 L1)。
|
||
|
||
**issue_assigned.md** 改为:
|
||
|
||
```markdown
|
||
Issue 指派
|
||
|
||
Issue: {issue_url}
|
||
标题: {issue_title}
|
||
标签: {labels}
|
||
|
||
📋 获取完整上下文(先读再动手):
|
||
- Issue 详情: GET {gitea_api}/repos/{repo}/issues/{issue_number}
|
||
- Issue 评论: GET {gitea_api}/repos/{repo}/issues/{issue_number}/comments
|
||
|
||
仓库: {repo_clone_url}
|
||
建议分支: feat/issue-{issue_number}-{brief}
|
||
```
|
||
|
||
**review_request.md** 改为:
|
||
|
||
```markdown
|
||
PR Review 请求
|
||
|
||
PR: {pr_url}
|
||
标题: {pr_title}
|
||
作者: {pr_author}
|
||
分支: {branch}
|
||
风险级别: {risk_level}
|
||
|
||
📋 获取完整上下文:
|
||
- PR diff: GET {gitea_api}/repos/{repo}/pulls/{pr_number}.diff
|
||
- PR 文件列表: GET {gitea_api}/repos/{repo}/pulls/{pr_number}/files
|
||
```
|
||
|
||
**review_result.md** 改为:
|
||
|
||
```markdown
|
||
Review {result}
|
||
|
||
PR: {pr_url}
|
||
标题: {pr_title}
|
||
审查者: {reviewer}
|
||
|
||
{review_body}
|
||
```
|
||
|
||
**ci_failure.md** 改为:
|
||
|
||
```markdown
|
||
CI 失败
|
||
|
||
Issue: {issue_url}
|
||
分支: {branch}
|
||
|
||
错误摘要:
|
||
{error_summary}
|
||
|
||
📋 CI 日志: {gitea_url}/{repo}/actions
|
||
修复后 push 会自动重触发 CI。
|
||
```
|
||
|
||
**deploy_failure.md** 改为:
|
||
|
||
```markdown
|
||
部署失败
|
||
|
||
仓库: {repo}
|
||
Commit: {commit_sha}
|
||
|
||
📋 排查步骤:
|
||
- CI 日志: {gitea_url}/{repo}/actions
|
||
- 服务器: pm2 logs {service_name}
|
||
```
|
||
|
||
**L2 代码改动**(`toolchain_routes.py`):
|
||
|
||
1. 从 Webhook payload 的 `repository` 对象提取 `clone_url` 和 `html_url`
|
||
2. `render_template()` 传入新变量:`gitea_api`、`gitea_url`、`repo_clone_url`
|
||
3. 所有模板变量统一补齐
|
||
|
||
##### L3:Skill 按需加载(不改 Skill 本身)
|
||
|
||
git-workflow、code-review 等 Skill 保持不变。
|
||
|
||
L1 的协作行为段里会引用 Skill 名称("参考 git-workflow Skill"),Agent 收到 Mail 后根据 L1 的引导自主加载对应 Skill。
|
||
|
||
**不改 Skill 路由机制**——靠 L1 的文案触发 Agent 的 Skill 路由器匹配。
|
||
|
||
---
|
||
|
||
### 三、新增功能:issue_comment @mention 通知
|
||
|
||
#### 3.1 设计
|
||
|
||
当前 `_handle_issue_comment` 只处理 `[CI]` 前缀评论。扩展为:
|
||
|
||
```
|
||
issue_comment 事件
|
||
├── 含 [CI] / CI 失败 → 原有 CI 失败通知逻辑
|
||
└── 含 @username → 解析 @mention → Mail 通知被 @的 Agent
|
||
```
|
||
|
||
#### 3.2 实现
|
||
|
||
**`toolchain_routes.py` 新增 `_handle_issue_comment_mention()`**:
|
||
|
||
```python
|
||
AGENT_IDS = {
|
||
"zhangfei-dev", "guanyu-dev", "zhaoyun-data",
|
||
"jiangwei-infra", "simayi-challenger", "pangtong-fujunshi",
|
||
}
|
||
|
||
# 前缀映射:@张飞 → zhangfei-dev
|
||
# 中文名映射:Agent 在 Gitea Issue 评论中可能用中文名 @mention
|
||
# 英文短名映射:Agent 可能用不带 -dev/-infra 后缀的短名
|
||
AGENT_ALIAS = {
|
||
"张飞": "zhangfei-dev",
|
||
"关羽": "guanyu-dev",
|
||
"赵云": "zhaoyun-data",
|
||
"姜维": "jiangwei-infra",
|
||
"司马懿": "simayi-challenger",
|
||
"庞统": "pangtong-fujunshi",
|
||
"pangtong": "pangtong-fujunshi",
|
||
"simayi": "simayi-challenger",
|
||
"zhangfei": "zhangfei-dev",
|
||
"guanyu": "guanyu-dev",
|
||
"zhaoyun": "zhaoyun-data",
|
||
"jiangwei": "jiangwei-infra",
|
||
}
|
||
|
||
def extract_mentions(body: str, sender: str) -> list[str]:
|
||
"""从评论 body 中提取 @mention 的 Agent ID"""
|
||
candidates = re.findall(r"@([a-zA-Z\u4e00-\u9fa5][a-zA-Z0-9\u4e00-\u9fff-]*)", body)
|
||
result = set()
|
||
for c in candidates:
|
||
# 精确匹配
|
||
if c in AGENT_IDS:
|
||
result.add(c)
|
||
# 前缀/别名匹配
|
||
elif c in AGENT_ALIAS:
|
||
result.add(AGENT_ALIAS[c])
|
||
else:
|
||
# 前缀模糊匹配:@zhangfei → zhangfei-dev
|
||
for aid in AGENT_IDS:
|
||
if aid.startswith(c):
|
||
result.add(aid)
|
||
break
|
||
# 过滤掉评论者自己
|
||
result.discard(sender)
|
||
return list(result)
|
||
```
|
||
|
||
**新增 mention 通知模板** `templates/toolchain/mention.md`:
|
||
|
||
```markdown
|
||
你在 Issue 中被 @mention
|
||
|
||
Issue: {issue_url}
|
||
评论者: {commenter}
|
||
评论内容:
|
||
{comment_body}
|
||
|
||
📋 获取完整上下文:
|
||
- Issue 详情: GET {gitea_api}/repos/{repo}/issues/{issue_number}
|
||
- Issue 评论: GET {gitea_api}/repos/{repo}/issues/{issue_number}/comments
|
||
```
|
||
|
||
**改动 `_handle_issue_comment`**:
|
||
|
||
```python
|
||
async def _handle_issue_comment(payload):
|
||
comment = payload.get("comment", {})
|
||
body = comment.get("body", "")
|
||
sender = comment.get("user", {}).get("login", "")
|
||
repo = _repo_fullname(payload)
|
||
issue = payload.get("issue", {})
|
||
|
||
# 原有 CI 失败逻辑(不变)
|
||
if "[CI]" in body or "CI 失败" in body:
|
||
# ... 原有逻辑 ...
|
||
|
||
# 新增:@mention 通知
|
||
mentions = extract_mentions(body, sender)
|
||
if mentions:
|
||
issue_number = issue.get("number", 0)
|
||
issue_title = issue.get("title", "")
|
||
text = render_template("mention", {
|
||
"repo": repo,
|
||
"issue_number": str(issue_number),
|
||
"issue_url": issue.get("html_url", ""),
|
||
"commenter": sender,
|
||
"comment_body": body[:500],
|
||
"gitea_api": "http://192.168.2.154:3000/api/v1",
|
||
})
|
||
title = f"@mention: {issue_title} ({repo}#{issue_number})"
|
||
for agent_id in mentions:
|
||
_send_mail(agent_id, title, text)
|
||
```
|
||
|
||
#### 3.3 去重
|
||
|
||
- 同一条评论 @多人:每人一封 Mail(不同 to,内容相同)
|
||
- 同一事件 org webhook + repo webhook 双触发:现有 delivery UUID 去重机制覆盖
|
||
- 同一人被 @多次:`extract_mentions` 返回 set,自动去重
|
||
|
||
---
|
||
|
||
### 四、Mail Spawn Prompt 改造
|
||
|
||
#### 4.1 问题
|
||
|
||
当前工具链 Mail 走 Mail 通道,spawn prompt 是:
|
||
|
||
```
|
||
你收到一封飞鸽传书(纯通知)。
|
||
发件者: system
|
||
主题: Issue 指派: xxx
|
||
内容: [工具链模板]
|
||
已阅即可。
|
||
```
|
||
|
||
"已阅即可"直接让 Agent 不做事。
|
||
|
||
#### 4.2 方案
|
||
|
||
**不改 MAIL_INFORM_TEMPLATE / MAIL_REQUEST_TEMPLATE 本身**(那是 Mail 系统通用的)。
|
||
|
||
改为:**工具链 Mail 使用 `type=request`(而不是默认的 inform)**。
|
||
|
||
在 `_send_mail()` 中,工具链事件创建的 Mail 默认 `performative=request`,这样 Agent 收到时走 `MAIL_REQUEST_TEMPLATE`,知道需要处理。
|
||
|
||
具体改动在 `_send_mail()` 函数或其调用处:工具链路由调用 `_send_mail` 时传入 `performative="request"`。
|
||
|
||
**⚠️ 验证要点**:改为 request 后,Agent spawn prompt 变为 "请处理以下请求",需确认:
|
||
1. Agent 不再把工具链 Mail 当纯通知忽略
|
||
2. Agent 能正确处理「已阅型」工具链事件(如 CI 失败通知——不需要回复,但需要知道)
|
||
3. 对已关闭 PR/Issue 的延迟通知,Agent 不会尝试去处理
|
||
|
||
验证方法:部署后发一条 Issue 指派 Mail,观察 Agent 行为是否符合预期。
|
||
|
||
---
|
||
|
||
### 五、完整改动清单
|
||
|
||
| # | 改什么 | 改动内容 | 层 | 风险 |
|
||
|---|--------|---------|---|------|
|
||
| 1 | 6 个 Agent 的 `AGENTS.md` | 加"工具链协作"段(内容统一) | L1 | 低(纯追加) |
|
||
| 2 | `templates/toolchain/issue_assigned.md` | 精简 + 加仓库上下文 + Gitea API 引导 | L2 | 低 |
|
||
| 3 | `templates/toolchain/review_request.md` | 精简 + 加 Gitea API 引导 | L2 | 低 |
|
||
| 4 | `templates/toolchain/review_result.md` | 精简 | L2 | 低 |
|
||
| 5 | `templates/toolchain/ci_failure.md` | 精简 + 加 CI 日志链接 | L2 | 低 |
|
||
| 6 | `templates/toolchain/deploy_failure.md` | 精简 + 加排查步骤 | L2 | 低 |
|
||
| 7 | **新建** `templates/toolchain/mention.md` | @mention 通知模板 | L2 | 低 |
|
||
| 8 | `src/api/toolchain_routes.py` | 提取 clone_url/html_url 传入模板;issue_comment 增加 @mention 解析;工具链 Mail 改为 request 类型 | L2 | 中 |
|
||
| 9 | 不改 | git-workflow 等 Skill 保持不变 | L3 | — |
|
||
| 10 | 不改 | daemon 核心逻辑、BootstrapBuilder、Skill 路由 | — | — |
|
||
|
||
---
|
||
|
||
### 六、验证方案
|
||
|
||
#### 6.1 单元验证
|
||
|
||
| 验证点 | 方法 |
|
||
|--------|------|
|
||
| `extract_mentions()` 提取 `@zhangfei-dev` | unit test |
|
||
| `extract_mentions()` 别名匹配 `@张飞` → zhangfei-dev | unit test |
|
||
| `extract_mentions()` 前缀匹配 `@zhangfei` → zhangfei-dev | unit test |
|
||
| `extract_mentions()` 过滤自己 | unit test |
|
||
| 模板渲染新变量不报错 | unit test |
|
||
|
||
#### 6.2 真实场景 E2E 验证
|
||
|
||
重复 Issue #32 的场景:
|
||
1. 创建 Issue 指派张飞
|
||
2. **验证**:张飞收到的 Mail 含 clone URL + Gitea API 引导
|
||
3. **验证**:张飞 spawn 后知道该做什么(L1 AGENTS.md 有流程引导)
|
||
4. **验证**:张飞有疑问时去 Gitea Issue 评论(而不是 Control UI)
|
||
5. 在 Issue 评论 @赵云
|
||
6. **验证**:赵云收到 @mention Mail
|
||
|
||
---
|
||
|
||
### 七、不做的事(标记为后续)
|
||
|
||
| 标记 | 描述 | 原因 |
|
||
|------|------|------|
|
||
| 后续-1 | Agent 离开工具链讨论后,是否有意识回到工具链 | 需要更多真实场景观察 |
|
||
| 后续-2 | 工具链使用标准在所有 Agent 间的一致性验证 | L1 统一段落是第一步,需要 E2E 验证 |
|
||
| 后续-3 | Mail 通道接入 BootstrapBuilder L2 注入 | 改动大,当前方案(L1 统一段落 + 模板引导)够用 |
|
||
| 后续-4 | Skill 路由器自动触发(引擎注入) | 改动 daemon 核心,当前靠 L1 文案触发 |
|
||
|
||
---
|
||
|
||
### 八、变更记录
|
||
|
||
| 日期 | 版本 | 变更 |
|
||
|------|------|------|
|
||
| 2026-06-09 | v1.0 | 初版:E2E 真实场景暴露问题 → 四层改造方案 + @mention 通知 + Mail type 改造 |
|
||
|