Files
sanguo_moziplus_v2/docs/design/13-toolchain-and-dev-workflow.md
T
cfdaily 3f07c528b6
CI / lint (pull_request) Successful in 7s
CI / test (pull_request) Successful in 28s
CI / notify-on-failure (pull_request) Successful in 1s
[moz] docs: 重写 §26 Gitea 协作规范设计(流水账→设计文档风格)
- §26 从落地流水账重写为正式设计文档
- 新增 26.1 设计目标(三个问题 + 目标)
- 新增 26.2 规范设计(标题规范/Label 体系/模板,含设计决策)
- 新增 26.3 执行机制(四层注入 + L2 priority 设计理由)
- 新增 26.4 Label 迁移策略(旧标签保留,不做批量迁移)
- 新增 26.5 与其他章节的交叉引用关系
- 保留 26.6 实现记录(压缩为记录而非主体)
- §4.5 末尾加交叉引用指向 §26
- P3 色值从 #c5def5 改为 #cfd3d7(解决与 type/impl 混淆)
2026-06-14 12:07:47 +08:00

143 KiB
Raw Blame History

三国团队工具链与开发流程设计

状态: 已完成(E2E 验证通过,所有 8 步 PASS) 作者: 庞统(副军师)🐦 评审: 司马懿(仲达)🗡️ 日期: 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.22026-06-11 从 v1.23.4 升级)
认证 HTTP + tokenadmin 账号(姜维持有)
权限 姜维持有 admin 权限(启用 Actions、分支保护、org webhook 等)
数据库 SQLite3
部署方式 DockerNAS 群晖),数据卷 /volume2/@docker/volumes/gitea-data/_data

2.2 CI/CDGitea Actions

配置
Runner Mac mini 裸机,gitea-runner v1.0.8(通过 PM2 管理 sanguo-act-runner
配置文件 .gitea/workflows/*.yml,每个项目自管
语法 兼容 GitHub Actionsv1.26.2 已验证支持 concurrency groups
触发 push / PR / tag / workflow_dispatch
v1.26 新增 concurrency groups、re-run failed jobs、可配置 GITEA_TOKEN 权限
仍不支持 failure()continue-on-errortimeout-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.zip765MB
  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 pushunable to create temporary file: Function not implemented
  • 修复:entrypoint 脚本在容器启动时自动从本地缓存降级 git 到 2.45.4
  • 持久化:/data/entrypoint-wrapper.sh + /data/git-2.45.4-r0.apk 在数据卷里,容器重建不丢失
  • 群晖内核无法通过 DSM 升级,内核版本跟硬件型号绑定

完整重建命令

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 执行有 bugSetup 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 常用命令

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 Protectionmain 分支)

  • 禁止直接 push
  • status checkCI / 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
  • URLhttp://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 曾临时关闭 webhookCI 错误大爆炸期间),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 分支:
    • 不允许直接 pushhotfix 除外)
    • PR 必须通过 CI(至少 unit + integration tests
    • PR 必须至少 1 人 Review(人或 Agent
    • hotfix 直接 push main 后必须触发 CI(至少 lint + unit),CI 失败则自动 revertM1 修订)
  • feature/bugfix 分支:
    • 无保护,自由 push
    • 合并前必须 CI 通过

3.4 Commit 规范

<type>(<scope>): <subject>

<body>
type 说明
feat 新功能
fix Bug 修复
impl 按设计文档实现
refactor 重构(不改行为)
test 测试相关
docs 文档
chore 构建/工具/配置

sanguo 组织所有仓库统一使用此 commit 规范。


§4. 问题管理

4.1 问题来源

来源 入口 处理方式
主公口头 庞统记录,经 Review 后 创建 Gitea Issue
司马懿 Review Review 意见 创建 Gitea Issue,关联 PR
自动化测试失败 CI 报告 自动评论到已有 Issue,或手动创建
用户测试反馈 人工报告 创建 Gitea Issue
Agent 运行发现 Agent 日志,经 Review 后 创建 Gitea Issue

4.2 Issue 标签体系

标签 颜色 色值 说明
type/bug #ee0701 Bug 修复
type/feat #84b6eb 新功能
type/impl 浅蓝 #c5def5 按设计实现
type/docs #fbca04 文档
type/test 绿 #0e8a16 测试
type/ci #d4c5f9 CI/CD
type/refactor #ff6f00 重构
priority/P0 深红 #b60205 紧急
priority/P1 #d93f0b
priority/P2 #fbca04
priority/P3 浅蓝 #c5def5

4.3 需求/问题 Review 前置

所有需求和问题在进入开发之前必须经过 Review:

类型 Review 者 目的
新需求 庞统(方案合理性)+ 司马懿(技术可行性) 确认需求清晰、可行、值得做
Bug/问题 司马懿(确认根因)或庞统(确认优先级) 确认是真 bug、不是误报,根因明确
Agent 发现的问题 庞统(确认有效性) 过滤误报

未经过 Review 的 Issue 不进入开发流程。

4.4 Issue 状态流转

Open → In Progress → Review → Closed
  ↑                                  ↓
  └──── Reopened ←───────────────────┘

4.5 标题规范

所有 Issue 和 PR 标题必须包含项目代号前缀,让人类一眼识别项目+类型:

Issue: [代号] type: 简述 PR: [代号] type(scope): 简述

项目代号:

仓库 代号
sanguo_moziplus_v2 moz
sanguo_quant_live quant
sanguo_vnpy vnpy

示例:

  • [moz] bug: Mail API 500 when comment_type invalid
  • [moz] impl(daemon): 知识注入 L2 引擎层 — WikiGuideSection
  • [quant] feat: 趋势跟踪策略骨架

此规范通过 L2 引擎层 GiteaConventionSectionpriority=55)自动注入所有 Agent prompt。 完整规范文档:L3 Skill gitea-conventions。规范设计原理详见 §26


§5. CI/CD 管道设计

5.1 通用 CI 流程(所有项目共享骨架)

每个项目在 .gitea/workflows/ci.yml 自定义具体步骤,但遵循统一骨架。

Gitea Actions v1.26.2 不支持 paths 过滤触发条件。通过路径判断放在 job 级别的 if 条件中,使用确定支持的语法。(M4 修订)

name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  # Job 1: 后端测试
  backend:
    # 只在有 Python 代码变更时跑
    runs-on: [self-hosted, mac-mini]
    steps:
      - uses: actions/checkout@v4
      - name: Setup Python
        run: |
          python3 -m venv .venv
          .venv/bin/pip install -e ".[dev]"
      - name: Lint
        run: .venv/bin/ruff check src/
      - name: Unit Tests
        run: .venv/bin/pytest tests/unit/ -m "not e2e" --tb=short
      - name: Integration Tests
        run: .venv/bin/pytest tests/integration/ --tb=short
      - name: Coverage Report
        run: .venv/bin/pytest --cov=src --cov-report=term-missing

  # Job 2: 前端构建
  frontend:
    # 只在有前端目录时跑
    runs-on: [self-hosted, mac-mini]
    steps:
      - uses: actions/checkout@v4
      - name: Install & Build
        run: |
          cd src/frontend
          npm ci
          npm run build
      - name: Type Check
        run: cd src/frontend && npx tsc --noEmit

实际使用时:每个项目根据实际情况删减 job。没有前端的删 frontend job,没有 Python 的删 backend job。

5.2 CI 门控级别

级别 触发条件 跑什么 阻断行为
快速 feature 分支 push lint + unit tests 软阻断:失败标红,创建 PR 时如果最近一次 CI 失败则不允许进入 Review(S2 修订)
标准 PR 到 main lint + unit + integration + coverage 阻断 merge
完整 tag / main push 标准 + buildE2E 仅 tag 触发) 阻断部署
手动 手动触发 完整 + 特定检查 按需

5.3 覆盖率渐进策略(仲达 Q3 补充)

当前多数项目从未跑过覆盖率,直接设阈值不现实。分三阶段:

阶段 时间 覆盖率策略 说明
P1 启用后 2 周 只报告不阻断 先拿基线数据
P2 启用后 1 月 40% 阈值,低于警告不阻断 逐步提升
P3 启用后 2 月+ 60% 阈值,低于阻断 正式生效

5.4 CI 结果反馈

  • 通过 → PR 可合并
  • 失败 → PR 阻断,自动评论失败信息
  • 覆盖率低于阈值 → P1 只报告,P2 警告,P3 阻断

§6. 代码审查流程

6.1 风险级别判定规则(M2 修订)

风险级别不由改动者自评,按以下规则自动判定 + 非改动者确认:

自动判定规则(在 CI 或 PR 模板中检查):

规则 级别 触发条件
涉及核心模块 改动文件匹配 **/spawner***/ticker***/dispatcher***/router***/guardrails***/strategy***/risk*
涉及资金/安全 改动包含交易逻辑、风控规则、密钥管理、资金操作
数据库 schema 变更 改动匹配 **/migrations/****/schema***/models.py 中的表定义
常规功能开发 标准 不匹配高风险规则的 src/ 下改动
文档/测试/配置 改动仅在 docs/tests/*.md*.yaml(非 guardrails

确认机制

  1. PR 创建时自动标注风险级别(基于 git diff 文件路径)
  2. 审查者(或庞统)确认或升级——只能升级不能降级
  3. 改动者可以申请升级(但不能申请降级)

6.2 审查方式(按风险分级)

风险级别 审查方式 审查者
固定 Review(必须) 司马懿(专职)
标准 固定 Review(必须) 司马懿(默认审查者)
简化走查 司马懿(快速确认)+ CI 通过

例外:司马懿自己的改动 → 庞统交叉审查。

所有级别的代码审查默认由司马懿执行。只有司马懿作为改动者时,才由庞统进行交叉审查。不再按改动领域分配审查者——简化流程,减少协调成本。

6.3 审查者分配规则

改动者 审查者
张飞 / 姜维 / 关羽 / 赵云 / 庞统 司马懿(默认)
司马懿 庞统(交叉审查)

6.4 审查清单(Skill 固化)

审查清单通过 Skill 提示词固化,不依赖审查者自觉。基础清单 + 项目类型变体

基础清单(所有项目通用)

1. 正确性
   - [ ] 代码逻辑正确,无死循环/空指针/边界遗漏
   - [ ] 异常处理完整(网络超时、文件不存在、参数非法)
   - [ ] 改动范围和 Issue/需求描述一致(不多不少)

2. 一致性
   - [ ] 命名规范统一(变量/函数/文件)
   - [ ] 和已有代码风格一致

3. 安全性
   - [ ] 无硬编码密钥/密码/Token
   - [ ] 用户输入有校验
   - [ ] 文件操作有路径校验

4. 可维护性
   - [ ] 复杂逻辑有注释(为什么,不是做什么)
   - [ ] 无重复代码(DRY)
   - [ ] 关键参数可配置,无魔法数字

5. 测试覆盖
   - [ ] 新功能有对应测试
   - [ ] Bug 修复有回归测试
   - [ ] CI 通过

结论:通过 / 不通过(附具体行号和修改方向)

量化策略项目变体(追加项):

6. 量化策略专项
   - [ ] 止损逻辑完整(触发条件、执行路径、异常兜底)
   - [ ] 仓位计算正确(滑点、手续费已计入)
   - [ ] 回测参数和实盘参数分离

6.5 审查时效

改动大小 期望审查时间
< 100 行 2 小时内
100-500 行 半天内
> 500 行 1 天内

§7. 部署流程

7.1 部署触发

触发方式 适用场景 流程
自动tag 触发) 正式发布 打 tag → CI 完整跑(含 E2E)→ 自动执行 deploy 脚本 → 健康检查
自动main push 日常迭代 push main → CI 标准跑 → 自动执行 deploy 脚本 → 健康检查
手动 紧急/调试 手动执行项目 deploy 脚本

7.2 项目部署脚本规范

每个项目必须提供 scripts/deploy.sh,接口统一:

#!/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

# .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 拦截)

7.6 部署成功通知(草案)

状态:草案,未实现。详细方案见 archive-3.0/22-cd-production.md

当前 deploy.yml 缺少部署成功后的 Mail 通知(CI 失败和 Deploy 失败通知已实现)。待实现方案:

  • deploy job 末尾追加通知 step
  • 查询 Gitea API 获取关联 PR 作者
  • 通过 Mail API 发送成功通知给 PR 作者 + pangtong-fujunshi
  • direct push 场景通知 jiangwei-infra + pangtong-fujunshi

§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 spawnopenclaw agent 走生产 openclaw openclaw 是全局单例,无法隔离

Agent spawn 走生产 openclaw 的决策理由:

  1. openclaw 是全局单例(~/.openclaw/ 只有一份),Agent 配置和 workspace 全局共享
  2. 测试 case 会标识测试上下文,Agent 记忆混淆风险低
  3. 完全隔离需要在 Docker 中跑独立 openclaw,维护成本不值得
  4. Agent 的 API 回写地址由 spawn message 传入,指向 CI 临时服务的端口 8084

路径硬编码修复清单

当前代码有 6 处硬编码绝对路径,需改为环境变量可配置(P1 必须完成):

位置 硬编码路径 改法 现状 行数
utils.py 数据根目录 BLACKBOARD_ROOT 环境变量 已支持 1
bootstrap.py:42 Skill 加载路径 MOZI_SKILL_PATH 环境变量 已支持 1
registry.py:264 项目扫描目录 ~/.openclaw/sanguo_projects SANGUO_PROJECTS_DIR 硬编码 1
spawner.py:1177,1261 ~/.openclaw/agents/<id>/sessions.json OPENCLAW_HOME 硬编码 2
blackboard_routes.py:161 ~/.openclaw/openclaw.json OPENCLAW_HOME 硬编码 1

共计 6 处(2 已支持、4 待修)。

环境总结

环境 位置 用途 生命周期
开发 ~/.openclaw/sanguo_projects/ 改代码、本地调试 常驻
CI 测试 CI runner 临时目录 lint/unit/integration/E2E CI 跑完即销毁
生产 ~/.sanguo_projects/ 正式运行服务 常驻

schema 变更检查(可选):CI 中加一步对比数据库 schema 变更,检测破坏性操作(删字段/改类型)。检测到则提 Issue 人工确认。可通过配置开关强制绕过。


§9. 完整开发流程(端到端)

9.0 全局工作流串联图

以下是把所有关键环节串起来的完整链路。三个场景(新功能/Bug修复/Hotfix)共享大部分节点,差异在分支。

┌─────────────────────────────────────────────────────────────────────────┐
│                        问题/需求入口                                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐               │
│  │ 主公口头  │  │ CI 失败   │  │ 司马懿   │  │ 运行时   │               │
│  │ /需求文档 │  │ 自动报告  │  │ Review   │  │ Agent发现 │               │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘               │
│       └──────────────┴──────────────┴──────────────┘                    │
│                              ↓                                         │
│                   ┌─────────────────────┐                              │
│                   │ 需求/问题 Review     │  ← §4.3                     │
│                   │                     │                              │
│                   │ 需求: 庞统(方案)     │                              │
│                   │       + 司马懿(可行) │                              │
│                   │ Bug: 司马懿(确认根因)│                              │
│                   │ Agent发现: 庞统(过滤)│                              │
│                   └──────────┬──────────┘                              │
│                              ↓ 通过                                    │
│  ┌───────────────────────────────────────────────────────────┐         │
│  │              创建 Gitea Issue #N                          │         │
│  │  标签: bug/feature/improvement + risk:high/standard/low   │         │
│  │  关联: PR / 复现步骤 / 根因分析                            │         │
│  └──────────────────────────┬────────────────────────────────┘         │
│                             ↓                                          │
│              ┌──── 场景分流 ────┐                                      │
│              ↓                  ↓                                      │
│     ┌─── Hotfix ───┐   ┌── 标准流程 ──┐                               │
│     │              │   │              │                               │
│     │ 直接 main    │   │ 分支开发     │                               │
│     │              │   │              │                               │
│     └──────┬───────┘   └──────┬───────┘                               │
│            ↓                   ↓                                       │
└─────────────────────────────────────────────────────────────────────────┘

=== 标准流程(新功能 / Bug修复)===

  ┌─────────────────────────────────────────────┐
  │ Step 1: 分支创建                             │
  │   git checkout -b feat/issue-N-xxx           │
  │   或 git checkout -b fix/issue-N-xxx         │
  │   角色Agent: 开发者(张飞/姜维/关羽/赵云)    │
  │   Skill: git-workflow                        │
  │   输入: Issue #N                             │
  │   输出: feature/bugfix 分支                  │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step 2: 编码 + 自测                          │
  │   编码 → 本地 pytest → commit                │
  │   角色Agent: 开发者                          │
  │   Skill: 按任务类型(无专门编码skill)        │
  │   输入: 分支 + Issue 需求                    │
  │   输出: 代码改动 + UT                        │
  │   约束: Bug修复必须加回归测试                 │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step 3: push → CI 快速门控(每次push       │
  │   Lint + Unit Tests                          │
  │   工具: Gitea Actions                        │
  │   输入: feature 分支 push                    │
  │   输出: ✅通过 / ❌失败标红                   │
  │   阻断: 软阻断(失败不阻止开发,但阻止PR进入Review)│
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step 4: 创建 PR                              │
  │   git push origin → Gitea 创建 PR            │
  │   自动: 风险级别标注(按改动文件路径)         │
  │   工具: Gitea PR                              │
  │   输入: feature 分支 + CI 快速门控结果        │
  │   输出: PR + 风险标签                         │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step 5: CI 标准门控(PR到main时)            │
  │   Lint + Unit + Integration + Coverage       │
  │   环境: CI 隔离临时环境                       │
  │   工具: Gitea Actions + pytest-cov + ruff    │
  │   输入: PR diff                              │
  │   输出: ✅通过 / ❌阻断merge                 │
  │   阻断: 硬阻断(CI不过不许merge)             │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step 6: 代码审查                             │
  │   审查者确认风险级别(只升不降)              │
  │   默认审查者: 司马懿(所有级别)              │
  │   例外: 司马懿的改动 → 庞统交叉审查          │
  │   Skill: code-review                         │
  │   输入: PR + CI 结果 + 风险级别               │
  │   输出: 通过 / 不通过(附行号+修改方向)      │
  │   不通过 → 回到 Step 2                       │
  └──────────────────┬──────────────────────────┘
                     ↓ 通过
  ┌─────────────────────────────────────────────┐
  │ Step 7: Merge → CI 完整门控                   │
  │   merge到main → 自动触发                     │
  │   标准 CI + deploy workflow                   │
  │   触发: Gitea branch protectionCI+Review通过才许merge)│
  │   输入: main 分支                            │
  │   输出: merge commit + CI 结果                │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step 8: 自动部署                              │
  │   deploy.sh --source=... --health-check      │
  │   记录版本到 deploy-history.jsonl             │
  │   角色Agent: 姜维(deploy.sh由项目维护)      │
  │   Skill: ci-cd-ops                           │
  │   输入: main 分支代码                         │
  │   输出: 部署结果 + 版本记录                   │
  │   失败: 自动回滚(deploy.sh --rollback     │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step 9: E2E 验证(CI 隔离环境自动跑)        │
  │   临时venv + 临时SQLite + 临时端口8084        │
  │   Agent spawn 走生产 openclaw(不隔离)      │
  │   角色: 司马懿写E2E用例,CI自动跑             │
  │   Skill: testing-workflow                    │
  │   输入: 部署后的代码                          │
  │   输出: E2E 结果                              │
  │   阻断: 初期不阻断,成熟后可阻断              │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step 10: Issue 关闭                          │
  │   部署成功 + 验证通过 → 关闭 Issue #N        │
  │   发现新问题 → 新开 Issue → 回到入口 Review  │
  └─────────────────────────────────────────────┘

=== Hotfix 分支流程 ===

  ┌─────────────────────────────────────────────┐
  │ Step H1: 确认 hotfix                         │
  │   庞统或主公确认(线上故障、核心功能受影响)  │
  │   Skill: hotfix-workflow                     │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step H2: 直接 push main                      │
  │   修改 → commit → push main                  │
  │   CI 必须触发(lint + unit),不可跳过       │
  │   CI 失败 → 自动 revert → 人工介入           │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step H3: 自动部署(同标准 Step 8)           │
  │   E2E 验证(同标准 Step 9)                  │
  └──────────────────┬──────────────────────────┘
                     ↓
  ┌─────────────────────────────────────────────┐
  │ Step H4: 24h 内复盘(必须)                  │
  │   补 Gitea Issue(根因 + hotfix 内容)       │
  │   司马懿 Review(事后审查代码质量)          │
  │   复盘记录(为什么需要 hotfix、如何避免)    │
  │   超时 → 下次 hotfix 权限暂停               │
  └─────────────────────────────────────────────┘

衔接机制:每一步怎么触发下一步

上图只画了"谁做什么",这里补清楚步骤之间靠什么机制衔接

衔接点 触发方式 通知方式 技术实现
问题入口 → 问题Review 庞统收到问题后主动发起 Mail 通知司马懿(需求类)/ 司马懿自己提交(bug类) Mail API POST /api/mail
问题Review → Issue创建 Review结论达成 庞统或司马懿创建 Gitea API POST /repos/{owner}/{repo}/issues
Issue创建 → 分支创建 庞统指派任务给开发者 Mail 通知被指派者,含 Issue #N + 分支名 Mail API + Agent 读取 Issue
分支创建 → 编码 开发者看到 Mail 后开始 无额外通知 Agent 启动时 Skill 注入分支信息
编码 → CI 快速门控 git push 自动触发 CI 结果显示在 commit 状态(Gitea Status Check 内置) Gitea Actions on: push
CI 快速门控 → PR创建 开发者主动(认为代码写完) 无自动通知 开发者在 Gitea Web UI 或 git push + 浏览器创建 PR
PR创建 → CI 标准门控 PR 创建/更新自动触发 CI 结果自动评论到 PR Gitea Actions on: pull_request
CI 标准门控 → 代码审查 CI 通过后 daemon Webhook 转发 Mail 给审查者 Mail 通知司马懿 Review Gitea Webhook pull_request → daemon Webhook 模块 → Mail API
代码审查 → 修改(不通过) 审查者提交 Review 意见 daemon Webhook 转发 Mail 通知改动者(附 Review 摘要) Gitea Webhook pull_request_review → daemon Webhook 模块 → Mail API
代码审查 → Merge(通过) 审查者点 Approve daemon Webhook 转发 Mail 通知改动者 merge Gitea Webhook pull_request_review → daemon Webhook 模块 → Mail API
Merge → 部署 merge 到 main 自动触发 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 Approvemerge 按钮灰色不可点
  • 配置位置:Gitea → 仓库 Settings → Branches → Branch Protection

4. Agent 间通知机制总结

场景 通知方式 为什么不用另一种
人→Agent任务指派 Mail Gitea Issue 不会主动通知 Agent,需要 Mail 推送
CI 结果 Gitea Status CheckPR页面)+ daemon Webhook 转发 MailAgent通知) Status Check 给人看,Mail 给 Agent 推送
Review 请求 daemon Webhook 转发 Mail Gitea Webhook 触发 → daemon 翻译成 Mail 给司马懿
Review 意见(不通过/通过) daemon Webhook 转发 Mail Gitea Webhook 触发 → daemon 翻译成 Mail 给改动者
CI 失败 CI workflow 写 PR评论 → daemon Webhook 转发 Mail CI 写评论是给所有人看,Webhook → Mail 给 Agent 推送
跨Agent协作请求 Mail Gitea 不支持跨 Agent 私信

9.0.1 通知模板(工具链通用版)

以下为 §9.0 衔接机制中用到的通用通知模板。§15.5 中还定义了更详细的流程强约束 Mail 模板(含完整流程步骤和 Gitea API 调用指令)。

所有自动化通知统一格式,确保 Agent 收到后能直接行动。

模板 1:任务指派(庞统 → 开发者)

From: pangtong-fujunshi
To: {被指派者}
Title: 任务指派: Issue #{N} - {Issue 标题}
Text:
  {Issue 标题}
  Issue: http://192.168.2.154:3000/{repo}/issues/{N}
  分支: feat/issue-{N}-{简述}
  风险级别: {high/standard/low}
  要点:
  - {需求要点1}
  - {需求要点2}
  验收标准:
  - {验收标准1}
  - {验收标准2}

模板 2CI 失败通知(CI workflow → 改动者)

From: daemon-webhook
To: {PR 作者}
Title: CI 失败: PR #{N} - {PR 标题}
Text:
  PR #{N} CI 未通过。
  PR: http://192.168.2.154:3000/{repo}/pulls/{N}
  失败测试:
  - {test_path}::{test_name} FAILED
  - {test_path}::{test_name} FAILED
  失败 step: {step_name}
  错误摘要: {error_output_tail_20_lines}
  请修复后重新 push。

模板 3:Review 不通过通知(审查者 → 改动者)

From: {审查者}
To: {改动者}
Title: Review 不通过: PR #{N} - {PR 标题}
Text:
  PR #{N} 审查未通过。
  PR: http://192.168.2.154:3000/{repo}/pulls/{N}
  风险级别: {confirmed_level}
  审查意见:
  1. [{文件}:{行号}] {问题描述} → {修改方向}
  2. [{文件}:{行号}] {问题描述} → {修改方向}
  必须全部修改后重新 push。

模板 4:部署失败回滚通知(CI workflow → 庞统/姜维)

From: daemon-webhook
To: pangtong-fujunshi, jiangwei-infra
Title: 部署失败已回滚: {repo} {commit_hash}
Text:
  {repo} 部署失败,已自动回滚到上一版本。
  触发 commit: {commit_hash}
  部署脚本: scripts/deploy.sh
  失败原因: {deploy_stderr_tail}
  当前版本: {previous_version}
  请人工介入排查。

模板 5:Bug 确认通知(司马懿 → 改动者)

From: simayi-challenger
To: {改动者}
Title: Bug 确认: Issue #{N} - {Issue 标题}
Text:
  根因: {根因描述}
  复现步骤: {steps}
  影响范围: {scope}
  风险级别: {level}
  Issue: http://192.168.2.154:3000/{repo}/issues/{N}
  分支: fix/issue-{N}-{简述}
  请修复并添加回归测试。

9.1 场景 A:新功能开发

1. 问题入口
   └─ 主公口头 / 需求文档
   └─ 庞统梳理需求方案 → 司马懿确认技术可行性
   └─ Review 通过后 → 庞统创建 Gitea Issue #N

2. 分支创建
   └─ git checkout -b feat/issue-N-xxx
   └─ Agent 或人工编码(git-workflow Skill 注入分支信息到 Agent 上下文)

3. 开发过程中
   └─ 每个 commit → push → CI 快速门控(lint + unit)
   └─ CI 失败 → 标红但不阻断开发 → 修好再 push

4. 开发完成
   └─ git push origin feat/issue-N-xxx
   └─ 创建 PR → 自动标注风险级别(基于改动文件路径)
   └─ CI 标准门控(lint + unit + integration + coverage

5. 审查
   └─ CI 通过后进入审查
   └─ 审查者确认风险级别(只升不降)
   └─ 司马懿审查(所有级别默认)
   └─ 如果改动者是司马懿 → 庞统交叉审查
   └─ 审查不通过 → 修改 → push → CI → 再审
   └─ 审查通过 → merge

6. 部署
   └─ merge 到 main → 自动触发 CI + deploy workflow
   └─ deploy.sh 执行 → 记录版本 → 健康检查
   └─ 部署成功 → Issue #N 关闭
   └─ 部署失败 → 自动回滚 → 人工介入

7. 验证
   └─ CI 隔离环境中自动跑 E2E(不污染生产)
   └─ 用户测试(手动)
   └─ 发现问题 → 新开 Issue(经 Review 后再开发)

9.2 场景 BBug 修复

1. 问题入口
   └─ CI 失败 / 司马懿 Review / 运行时发现
   └─ 司马懿确认根因(是真 bug 不是误报)
   └─ 创建 Gitea Issue #N,标注 bug + 风险级别

2. 根因定位(bugfix-workflow Skill
   └─ 调查根因(不猜,看日志/复现)
   └─ 在 Issue 中记录根因

3. 修复
   └─ git checkout -b fix/issue-N-xxx
   └─ 修改代码 + 添加回归测试(必须)
   └─ push → PR → CI → Review

4. 合并+部署
   └─ merge → auto deploy → 健康检查
   └─ Issue #N 关闭

5. 验证
   └─ 确认原 bug 不再复现

9.3 场景 C:紧急修复(hotfix)(M1/S5 修订)

1. 判断是否需要 hotfix
   └─ 标准:线上故障影响核心功能,等 PR 流程代价过高
   └─ 庞统或主公确认

2. 直接在 main 上修
   └─ git commit → push main
   └─ CI 必须触发(lint + unit),不可跳过
   └─ auto deploy → 健康检查
   └─ CI 失败 → 自动 revert → 人工介入

3. 事后复盘(hotfix-workflow Skill
   └─ 24 小时内必须完成:
       ├─ 补 Gitea Issue(记录根因 + hotfix 内容)
       ├─ 司马懿 Review(事后审查代码质量)
       └─ 复盘记录(为什么需要 hotfix、如何避免)
   └─ 超时未复盘 → 下次 hotfix 权限暂停

4. E2E 验证
   └─ CI 隔离环境中自动跑 E2E 确认 hotfix 有效

§10. Skill 清单(流程固化)(S4 修订)

以下流程通过 Skill 固化到对应 Agent

Skill 名 固化什么 适用 Agent
git-workflow 分支创建/命名/合并规范、commit 规范、PR 创建流程、当前分支注入上下文 所有编码 Agent
code-review 风险级别判定规则、审查清单(基础+项目变体)、审查标准、结论格式 司马懿(默认审查者)、庞统(交叉审查)
ci-cd-ops CI 配置规范、deploy 脚本规范、健康检查标准、版本记录规范 姜维(平台)、所有项目维护者
bugfix-workflow 根因定位流程、回归测试要求、Issue 关联 所有编码 Agent
hotfix-workflow hotfix 判断标准、直接 push main 流程、CI 必跑、24h 复盘要求 庞统(协调)、张飞、姜维
testing-workflow 测试分工规范、什么时候跑什么测试、测试数据隔离 所有 Agent
release-workflow tag 规范、changelog、发布流程、数据库 schema 变更向前兼容规范 庞统(协调)

测试分工规范(testing-workflow Skill 内)

测试类型 谁写 谁跑
单元测试(UT 开发者自己(张飞/姜维/关羽/赵云等) CI 自动 + 开发者本地
功能测试 司马懿(专职) CI 自动
集成测试 司马懿(专职) CI 自动
E2E 测试 司马懿(专职) CI 自动(隔离环境)+ 手动触发
测试脚本/工具 司马懿 + 赵云(数据相关) 按需

所有人都能写代码和写 UT。但功能测试、集成测试、E2E 由司马懿专职编写和维护——确保测试视角独立于开发视角。


§11. 实施路线

阶段 内容 前置条件 耗时
P0: 基础启用 Gitea admin 开启 Actions + 部署 act-runnerMac mini 裸机) 主公操作 NAS 30 分钟
P1: CI 骨架 模板 CI workflow + pytest-cov + ruff + 覆盖率基线测量 P0 1 天
P2: 流程 Skill 7 个 Skill 编写(见 §10 2-3 天
P3: 自动部署 部署 workflow + 项目 deploy.sh 规范化 + 版本记录 P1 1-2 天
P4: 渐进收紧 覆盖率阈值 P1→P2→P3、E2E 集成 P1-P3 按需

P1 必须先就位的工具(否则机械门控跑不起来):

  • rufflint
  • pytest-cov(覆盖率)
  • act-runnerCI 执行器)

§12. 评审记录

v0.1 → v0.2 修订清单

编号 类型 问题 修订内容
M1 必须修 hotfix 安全网不足 §3.3 明确 hotfix push main 后 CI 必跑,失败自动 revert;§9.3 统一流程
M2 必须修 风险级别谁来定 §6.1 新增自动判定规则(按文件路径)+ 非改动者确认机制(只升不降)
M3 必须修 回滚策略过于简单 §7.2 新增 --version 参数和 deploy-history.jsonl;§7.5 重写回滚策略
M4 必须修 CI 语法问题 §5.1 删除 hasPython()/hasFrontend() 伪函数,改为每个项目按需删减 job
S1 建议改 轮查配对不合理 §6.3 改为按改动领域分配,不再固定轮转
S2 建议改 快速门控"不阻断"是噪音 §5.2 改为"软阻断"PR 进入 Review 前检查最近 CI 状态
S3 建议改 三阶段门控说明不清 §8.2 补充:快速门控是开发辅助,PR 合入必须标准门控
S4 建议改 Skill 遗漏 §10 增加 testing-workflow、hotfix-workflow、release-workflow(含 schema 变更)
S5 建议改 hotfix 复盘无时限 §9.3 加 24 小时复盘约束,超时暂停 hotfix 权限
Q1 评审回答 Trunk-Based 够用吗 §3.1 补充前提:Agent 必须知道当前分支,git-workflow Skill 注入
Q3 评审回答 覆盖率基线缺失 §5.3 新增渐进策略(P1 只报告 → P2 40% → P3 60%
Q6 评审回答 机械门控前提未就绪 §8.4 新增工具清单,§11 标注 P1 必须先就位

v0.3 → v0.4 修订清单(主公审阅反馈)

编号 反馈 修订内容
F1 需求/问题必须经 Review §1 新增原则 7;§4.3 新增需求 Review 前置规则;§9.1 场景 A 加入 Review 步骤
F2 E2E 仅用户触发 §8.1 E2E 改为“仅用户手动触发”;§8.2 门控说明移除 E2E 自动跑;§9.1/9.3 验证步骤更新
F3 编码/测试人人能做,功能/集成/E2E 司马懿专职 §10 新增测试分工规范表
F4 审查默认司马懿 §6.2 简化为司马懿统一审查,仅司马懿自身改动由庞统交叉审查
F5 以项目为单位组织 §1 新增原则 8;Issue 标签体系含项目维度
F6 姜维持有 Gitea admin §2.1 权限更新
F7 分支保护含义说明 §3.3 补充分支保护规则说明
F8 schema 变更能自动化就自动化 保留在 release-workflowCI check + Issue 兜底

v0.4 → v0.5 修订清单(主公二次审阅)

编号 反馈 修订内容
G1 E2E 不自动跑是因为怕污染生产,不是不需要 §8.5 新增 CI 隔离测试环境,E2E 改为自动跑(临时 venv + 临时 SQLite + 临时端口)
G2 三套环境是否有必要 §8.5 环境总结表:开发/CI临时/生产,不需要常驻测试环境
G3 schema 变更检查可以,可选+开关 §8.5 补充 schema 检查说明:可选 + 配置开关强制绕过
G4 分支保护确认开启 §14 更新为已确认
G5 E2E 触发方式 §8.1 更新:CI 自动 + 手动触发

v0.5 → v0.6 修订清单(主公三次审阅)

编号 反馈 修订内容
H1 Agent spawn 走生产 openclaw,不隔离 §8.5 新增隔离边界表:程序逻辑隔离 vs Agent 走生产
H2 路径硬编码问题 §8.5 新增路径硬编码修复清单(6 处,2 处已支持、4 处待修)

v0.6 → v1.0 定稿(司马懿终审通过)

  • 隔离边界表增加"缓解措施"列(仲达建议)
  • 路径硬编码清单增加"行数"列(仲达建议)
  • 状态标记为 v1.0 定稿

v1.0 → v1.1(主公要求:补全局工作流串联图)

  • §9.0 新增全局工作流串联图:从问题入口到部署上线的完整链路
  • 每个节点标注:谁做、用什么工具、什么Skill、输入输出
  • 标准流程 10 步 + Hotfix 分支 4 步
  • 新增工具链衔接总览表
  • 新增衔接机制详解:每一步怎么触发下一步、靠什么技术、怎么通知
  • §9.0.1 新增 5 个通知模板(任务指派/CI失败/Review不通过/部署回滚/Bug确认)
  • 修正:Agent 不看 Gitea PR 评论,所有需 Agent 行动的场景都走 Mail 推送

v1.2 → v2.0(串联架构重构)

  • §15 新增完整串联架构:Gitea 自动化 + Daemon Webhook 模块 + Mail 执行层
  • 核心变更:ci-notifier 取消,Webhook 转发集成到 daemon
  • 新增:超时检测(三级处置)+ 失败处置(走 Issue 流程)
  • 新增:Mail 模板(流程强约束固化)
  • 新增:Skill vs Mail 边界明确
  • 新增:Agent ID ↔ Git 用户名映射
  • §9.0 衔接机制表更新:所有 ci-notifier 引用替换为 daemon Webhook 模块
  • §9.0.1 通知模板 From 字段更新:ci-notifier → daemon-webhook

v2.0 司马懿终审修订

编号 类型 问题 修订内容
M1 必须修 超时阈值统一 4h 不合理 §15.4 改为从 Mail metadata 读取个性化 deadline,三级处置按超时程度而非绝对时间
S1 建议 Webhook 代码缺少映射步骤 §15.3 代码示例加 to_agent_id() 映射函数,所有 send_mail 调用都经映射
S2 建议 Webhook 缺签名验证 §15.3 代码示例加 X-Gitea-Signature HMAC 验证
S3 建议 缺 Bug 确认模板 §15.5 新增模板 6(Bug 确认通知)
  • §14 待讨论更新:已确认事项标记,新增 Webhook 模块待讨论项

v2.0 实施记录(2026-06-06

# 实施项 状态 备注
1 Gitea Actions 能力调研 docs/research/gitea-actions-research.md
2 Mail API 回复能力调研 docs/research/mail-reply-research.md
3 路径硬编码修复 4处改为环境变量,411 tests passed
4 CI workflow 编写 ci.yml + deploy.yml + e2e.yml
5 Skill 文件编写 7个共通技能
6 Gitea 仓库创建 sanguo/moziplus-v2(姜维)
7 act-runner 部署 arm64 裸机 + LaunchAgent(姜维)
8 分支保护配置 main: CI通过+1人Review+禁止force push(姜维)
9 Webhook 配置 已配未启用(等 daemon 集成)(姜维)
10 CI Secret 配置 CI_TOKEN(注意:Gitea 保留 GITEA_* 前缀)
11 Workflow 变量前缀验证 gitea.* 正确,无需修改
12 Mail 模板验证 5项能力全验证通过
13 PR #1 创建 feat/initial-setup → main
14 开发→安装目录同步 diff 确认一致
- Webhook daemon 集成 需改 daemon 代码,暂不做
- Mail 模板自动发送(Webhook 触发) 依赖 Webhook daemon 集成
- act-runner LaunchAgent 网络就绪优化 偶尔 gRPC 连接失败
- Gitea 升级到 v1.24+ 💡 建议,非必须

§13. 项目维度组织

13.1 多项目并行

所有内容以项目为单位组织:

维度 按项目组织
代码仓库 Gitea 上每个项目一个 repo
Issue 每个项目的 Issue 独立管理
CI/CD 每个项目自管 .gitea/workflows/
部署脚本 每个项目自管 scripts/deploy.sh
测试 每个项目自管 tests/ 目录
Skill 配置 通用 Skill 共享,项目特定 Skill 放项目仓库

特殊:_general 项目用于跨项目的通用任务(如工具链基础设施维护)。


§14. 待讨论

  1. Gitea admin 权限:姜维持有 admin 权限,可负责启用 Actions、配置分支保护等
  2. 分支保护:已确认开启——PR 必须 CI 通过 + 至少 1 人 Review 才能合并到 main
  3. 数据库 schema 变更检查:CI 自动检测破坏性变更(可选,有开关可强制绕过),检测不到的提 Issue 人工干预 — 已确认
  4. 环境架构:三环境(开发/CI临时/生产),CI 临时环境支持 E2E 自动跑不污染生产 — 已确认
  5. Webhook 转发模块集成到 daemon:已确认方向,具体实现待设计(见 §15)

§15. 串联架构

§15 是 v2.0 的核心新增章节,定义工具链三层串联架构:Gitea 自动化 + Daemon Webhook 模块 + Mail 执行层。 此架构取消了之前的 ci-notifier 虚拟角色概念,所有事件流转通过三层清晰分工完成。

§15.1 串联架构总览

三层架构图:

Layer 1: Gitea 自动化(流程引擎)
  - Actions: push → CI, PR → CI, merge → deploy
  - Status Check: CI 结果自动显示在 PR
  - Branch Protection: CI通过+Review通过才许merge
  - CI workflow 内可调 Gitea API 写 PR 评论

Layer 2: Daemon 流程编排(转发+监控)
  - Webhook 接收模块:监听 /webhook 端点
  - 简单事件:Webhook → 解析 → 直接发 Mail
  - 复杂事件:Webhook → 解析 → spawn 庞统作为任务节点
  - 超时检测:在 ticker 中扫描未回复 Mail
  - 失败处置:轻度重发提醒,中度 spawn 庞统,严重创建 Issue

Layer 3: Mail 执行层(Agent 接口)
  - Mail = 流程指令 + 回复确认 + 时限
  - Agent 收到 Mail 后行动,完成后回复确认
  - Mail 线程 = Issue 生命周期可追溯
  - 流程强约束固化在 Mail 模板中

核心原则:

  1. Gitea 是流程引擎——所有状态流转靠 Gitea 自己(Actions + Status Check + Branch Protection
  2. Webhook → Mail 转发集成在 daemon 里——状态共享,可直接触发下一步
  3. Mail 是 Agent 接口——Agent 不看 Gitea UI,所有需 Agent 行动的事件通过 Mail 推送
  4. 简单事件 daemon 直接处理,复杂事件(多 Agent 协作)spawn 庞统作为协调节点
  5. 没有 ci-notifier——不需要虚拟角色,Gitea 是流程引擎,daemon 是翻译器+监控器

§15.2 Gitea Webhook 能力清单

事件类型 触发时机 Payload 关键字段 工具链用途
push 代码推送 commit hash, 分支, 作者 不需要转发(Actions 自动处理)
pull_request (opened) PR 创建 PR ID, 标题, 分支, 作者 → Mail 通知司马懿 Review
pull_request_review (submitted) Review 提交 PR ID, 审查者, 结论(APPROVE/REQUEST_CHANGES), 评论 → Mail 通知张飞 Review 结果
pull_request (closed/merged) PR 合并 PR ID, 合并 commit Mail 通知 PR 作者合并完成(PR #38 恢复)
issue_comment PR/Issue 评论 评论者, 内容 CI workflow 写的失败评论 → 转发 Mail
issues (opened+assigned) Issue 创建/指派 Issue ID, 标题, 被指派人 → Mail 通知开发者
release Release 创建 tag, 名称 触发完整 CI+部署

说明Webhook 是单向 POSTGitea 发完不管。需要在 daemon 里实现 /webhook 端点接收。

§15.3 Daemon Webhook 模块设计

在 daemon 中新增一个 Webhook 接收模块(FastAPI 路由):

# 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 根据结果触发下一步)

# 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 没回复时的处置)

async def check_stale_flows():
    """扫描超时未回复的 Mail,按个性化 deadline 判断"""
    pending_mails = queries.find_pending_requests()
    
    for mail in pending_mails:
        deadline_hours = mail.meta.get("deadline_hours", 8)  # 默认 8 小时
        hours_elapsed = (now() - mail.created_at).total_seconds() / 3600
        
        if hours_elapsed < deadline_hours:
            continue  # 未超时,跳过
        
        hours_overdue = hours_elapsed - deadline_hours
        
        if hours_overdue < 4:
            # 轻度:重发 Mail 提醒
            await send_mail(to=mail.to, title=f"提醒: {mail.title}", 
                          text=f"此任务已超时 {hours_overdue:.0f} 小时,请尽快处理。")
        
        elif hours_overdue < 16:
            # 中度:spawn 庞统介入
            await spawn_agent("pangtong-fujunshi", 
                            f"流程卡住: Mail {mail.id} '{mail.title}' 超时 {hours_overdue:.0f} 小时未回复。请介入协调。")
        
        else:
            # 严重:创建 Issue + 通知庞统
            await create_issue(title=f"流程严重超时: {mail.title}", labels=["blocked", "priority:high"])
            await send_mail(to="pangtong-fujunshi", 
                          title=f"严重超时: {mail.title},已创建 Issue", ...)

失败处置原则

  • 所有失败 → Issue 走正式流程,不私下通知 Agent 悄悄解决
  • CI 失败 → CI workflow 写 PR 评论 → Webhook → Mail 通知 → Agent 修 → 走标准 PR 流程
  • 部署失败回滚 → CI workflow 创建 Issuepriority:high → 走标准 Bug 修复流程
  • 超时未响应 → 三级处置(提醒→庞统介入→Issue+通知主公)

断点续传

  • 每个环节的状态都在 Gitea 上(PR 状态、CI 结果、Review 状态)
  • Mail 线程提供完整的可追溯记录
  • 回复驱动确保正常流程自动推进,超时检测确保异常被捕获
  • 流程卡住时,庞统可以根据 Mail 线程 + Gitea 状态快速定位断点并继续

§15.5 Mail 模板(流程强约束固化)

注意Mail 模板里固化的是流程强约束——这个工作链特有的一步步怎么做。 共通技能(git 操作、审查清单等)放在 Skill 里(见 §10)。

模板 1:任务指派(庞统/daemon → 开发者)

Title: 任务指派: Issue #{N} - {标题}
Text:
  Issue: {url}
  分支: {branch_name}
  风险级别: {level}
  验收标准: {criteria}
  
  流程:
  1. 创建分支 {branch_name}
  2. 编码 + 写 UT
  3. push → 等 CICI 会自动跑)
  4. CI 通过后创建 PRGitea API: POST /repos/{owner}/{repo}/pulls
  5. 等 Review(司马懿会自动收到通知)
  6. Review 通过后 merge PR
  
  如果 CI 失败:修复后重新 push,回到步骤 3
  如果 Review 不通过:按意见修改后重新 push,回到步骤 3
  
  完成后回复此 Mail 确认。
  时限:{deadline}

模板 2Review 请求(Webhook → 司马懿)

Title: Review 请求: PR #{N}
Text:
  PR: {url}
  标题: {title}
  风险级别: {level}
  改动文件: {file_list}
  
  流程:
  1. 读取 PR diffGitea API: GET /repos/{owner}/{repo}/pulls/{N}.diff
  2. 按审查清单审查(参考 code-review Skill
  3. 提交 ReviewGitea API: POST /repos/{owner}/{repo}/pulls/{N}/reviews
     - APPROVE 或 REQUEST_CHANGES
  4. 提交后张飞会自动收到通知
  
  完成后回复此 Mail 确认。
  时限:{deadline}

模板 3Review 结果通知(Webhook → 张飞)

Title: Review {result}: PR #{N}
Text:
  PR: {url}
  结果: {APPROVED / REQUEST_CHANGES}
  审查意见: {review_body}
  
  {如果通过}:
  流程:merge PRGitea API: POST /repos/{owner}/{repo}/pulls/{N}/merge
  
  {如果不通过}:
  流程:
  1. 按审查意见修改代码
  2. push → CI 自动跑
  3. CI 通过后等重新 Review
  
  完成后回复此 Mail 确认。

模板 4CI 失败通知(Webhook from issue_comment → 张飞)

Title: CI 失败: PR #{N}
Text:
  PR: {url}
  失败测试: {failed_tests}
  错误摘要: {error_summary}
  
  流程:
  1. 查看完整 CI 日志(PR 页面)
  2. 修复失败的测试
  3. push → CI 自动重跑
  
  完成后回复此 Mail 确认。

模板 5:部署失败通知(→ 庞统,复杂事件 spawn)

Title: 部署失败已回滚: {repo}
Text:
  仓库: {repo}
  触发 commit: {sha}
  失败原因: {error}
  已自动回滚到: {previous_version}
  
  已自动创建 Issue #{M}。
  请协调排查。

模板 6:Bug 确认通知(司马懿 → 改动者)

Title: Bug 确认: Issue #{N} - {标题}
Text:
  Issue: {url}
  根因: {根因描述}
  复现步骤: {steps}
  影响范围: {scope}
  风险级别: {level}
  分支: fix/issue-{N}-{简述}
  
  流程:
  1. 创建分支 fix/issue-{N}-{简述}
  2. 修复 bug + 写回归测试
  3. push → CI 自动跑
  4. CI 通过后创建 PR
  5. 等 Review
  
  完成后回复此 Mail 确认。
  时限:{deadline}

§15.6 Skill vs Mail 边界

维度 Mail 模板 Skill
作用域 工具链流程特有 全局,所有项目通用
强制度 强约束,Agent 必须按流程走 按需加载,提升质量
内容 流程步骤+Gitea API 调用指令 共通技能(审查清单、git规范等)
示例 "CI失败→修→push→等CI→等Review" "怎么写好 UT"、"审查清单"
没有 Skill 能否运转 能(流程不会断) 只是质量没那么高

原则:没有 Skill,流程还能运转。有了 Skill,流程运转得更好。

Skill 只放共通的、所有项目都能用的技能:

  • git-workflow:分支命名、commit 规范(所有项目通用)
  • code-review:审查清单(所有项目通用)
  • testing-workflow:测试规范(所有项目通用)
  • bugfix-workflow:根因定位方法(所有项目通用)

工具链特有的流程("CI 失败后怎么做"、"Review 通过后怎么做")全部固化在 Mail 模板里。

§15.7 Agent ID = Git 用户名(已确认一致)

姜维确认(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 类型 中枢产出就是 Mailfrom=system, type=inform),模板填充 description,投递复用 spawner。区别只在内容(模板化)和元数据(结构化),不需要新的数据结构
D2 提示词独立模板文件 模板和代码分离,改文案不改代码,可审查可扩展
D3 中枢是 daemon 内模块 要访问 Blackboard 创建 Task,共享进程。用独立路由模块(toolchain_routes.py)保持职责清晰
D4 同步处理 事件处理很轻(解析+模板填充+创建Task),远在 Gitea 5秒超时内。复杂事件未来用 asyncio.create_task
D5 Agent ID = Gitea 用户名,直用 姜维确认:Gitea 注册用户名就是完整 Agent IDzhangfei-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

PR Review 请求

PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number}
标题: {pr_title}
作者: {pr_author}
分支: {branch}
风险级别: {risk_level}
改动文件:
{file_list}

流程:
1. 读取 PR diffGitea API: GET /repos/{repo}/pulls/{pr_number}.diff
2. 按审查清单审查(参考 code-review Skill
3. 提交 ReviewGitea API: POST /repos/{repo}/pulls/{pr_number}/reviews
4. 提交后改动者会自动收到通知

完成后回复此 Mail 确认。

3.3 模板变量提取

变量 来源 提取方式
pr_number Webhook payload event["pull_request"]["number"]
pr_title Webhook payload event["pull_request"]["title"]
pr_author Webhook payload to_agent_id(event["pull_request"]["user"]["login"])
branch Webhook payload event["pull_request"]["head"]["ref"]
file_list Webhook payload event["pull_request"]["changed_files"] 或需要额外 API 调用
risk_level daemon 计算 按文件路径规则匹配(见 §3.4
repo Webhook payload event["repository"]["full_name"] 提取

3.4 风险级别自动判定(简化版)

按改动文件路径匹配规则:

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 接口设计

# 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-SHA256Gitea 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。

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 共用函数

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 Taskfrom=system, type=inform
    复用 mail_notify.py 的模式:直接通过 Blackboard 创建 Task。
    """
    # 从 mail_routes.py 提取共用逻辑,或直接复用 mail_notify 的方式
    ...

def is_duplicate(event_key: str) -> bool:
    """幂等检查:同一事件不重复创建 Mail
    存储在 SQLite events 表,daemon 重启后仍有效。
    保留最近 7 天记录,ticker 定期清理过期记录。
    """
    ...

def calc_risk_level(files: list[str]) -> str:
    """按文件路径规则判定风险级别"""
    ...

def get_changed_files(pr: dict) -> list[str]:
    """从 PR payload 提取改动文件列表
    payload 只有 changed_files 数量,需要额外调用 Gitea API:
    GET /repos/{owner}/{repo}/pulls/{number}/files
    """
    ...

4.5 模板引擎

# 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 正文 200handler 内部处理)
模板填充失败 日志 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 jobci.yml),当前格式:

❌ **CI 失败**

请检查 CI 日志并修复。

触发 commit: `abc1234`

需要修改:将 comment 前缀统一为 [CI],方便中枢匹配:

# .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

# .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 = 22760993dff898a190731da43aa8d964daemon 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 API3 次重试 + 失败信息写入 Mail正文
6 Deploy workflow 通知方式 已确认 当前创建 Issue(非 PR comment)。部署通知走 issues Webhookissue created)而非 issue_comment
7 签名算法 已确认 Gitea 使用 HMAC-SHA256,代码注释已补
8 Webhook 作用范围 组织级 Gitea 组织级 webhookHook 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 ~1705个文件) 新增
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(组织级覆盖)和 #9ALLOWED_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 前置条件 Checklist6 项)
§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 descriptionAgent 按需加载 按需 复用 L2 Skill,不新建

核心原则

  • L1 给操作手段("怎么做"):curl 命令模板、参数说明
  • L2 给流程规范("什么时候做什么"):审查清单、分支规范、测试策略
  • L3 是 L2 的被动触发版本,不需要单独维护

§17.3. L1TOOLS.md Gitea 操作模板

§17.3.1 设计原则

  1. 开箱即用:收到 Mail 后直接复制粘贴 curl 命令即可执行,不需要查文档
  2. 按角色定制:不是每个 Agent 都需要全套 API,只给该角色需要的
  3. 统一格式:所有模板使用相同的环境变量约定

§17.3.2 公共变量约定

每个 Agent 的 TOOLS.md 头部加入:

## 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
### 创建 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

curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls/{pr_number}.diff" \
  -H "Authorization: token $GITEA_TOKEN"

Merge PRReview 通过后)

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 状态

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 改动文件列表

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

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

curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/labels" \
  -H "Authorization: token $GITEA_TOKEN"

查询 PR 列表

curl "http://192.168.2.154:3000/api/v1/repos/sanguo/{repo}/pulls?state=open" \
  -H "Authorization: token $GITEA_TOKEN"

创建 Release

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

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

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. L2Skill 升级到 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.ymlpush 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

  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
调研发现

发现 1Gitea Review API event 枚举值

Gitea ReviewStateType 枚举值应为 APPROVED(不是 APPROVE)。使用错误的枚举值会创建 PENDING review,且 PENDING 不触发 webhook 通知。

  • 正确:{"event": "APPROVED"}
  • 错误:{"event": "APPROVE"} → 创建 PENDING reviewwebhook 不触发

已修正:TOOLS.md 模板 + code-review Skill 中的 event 值。

发现 2Gitea 1.23.4 支持 PullRequestReview webhook

之前误判为"不支持",实际原因同发现 1——错误的枚举值创建了 PENDING reviewPENDING 不在 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 作者

发现 3act-runner 进程管理

act-runner 未纳入进程管理,崩溃后不会自恢复。姜维已修复:

  • 纳入 PM2 托管(进程名:sanguo-act-runnerid=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 精简,实验项目只需要基本验证:

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-missingpush 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.md6 个 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 已有 coverageci.yml 有意不加

v3.0 → v3.1 变更(P3 端到端验证 + 调研结论)

编号 变更内容
§17.3.3 Review API event 枚举值修正:APPROVE → APPROVED
§17.6.3 注意更新:runs-on 已改为 macos-arm64placeholder 已替换
§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 webhookreview.state/body/user)和 org webhookreview.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.md5 行信息,无流程引导;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 各层具体内容

L1AGENTS.md 加工具链协作行为段(所有 Agent 统一)
## 工具链协作(Gitea

收到 Gitea 事件通知(Issue 指派、Review 请求、CI 失败等)时,按以下流程操作:

### 基本流程
- **Issue 指派** → clone 仓库 → 开分支 → 编码 → 提 PR(参考 git-workflow Skill
- **Review 请求** → 读 PR diffGitea 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 改为:

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 改为:

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 改为:

Review {result}

PR: {pr_url}
标题: {pr_title}
审查者: {reviewer}

{review_body}

ci_failure.md 改为:

CI 失败

Issue: {issue_url}
分支: {branch}

错误摘要:
{error_summary}

📋 CI 日志: {gitea_url}/{repo}/actions
修复后 push 会自动重触发 CI。

deploy_failure.md 改为:

部署失败

仓库: {repo}
Commit: {commit_sha}

📋 排查步骤:
- CI 日志: {gitea_url}/{repo}/actions
- 服务器: pm2 logs {service_name}

L2 代码改动toolchain_routes.py):

  1. 从 Webhook payload 的 repository 对象提取 clone_urlhtml_url
  2. render_template() 传入新变量:gitea_apigitea_urlrepo_clone_url
  3. 所有模板变量统一补齐
L3Skill 按需加载(不改 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()

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

你在 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

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 改造

§26. Gitea 协作规范设计

状态: 已实现(PR #69 日期: 2026-06-14

26.1 设计目标

团队三个仓库(moziplus_v2 / quant_live / vnpy)在 Gitea 上独立存在,协作时存在三个问题:

  1. 标题不可辨识Fix mail API 看不出是哪个项目、什么类型的改动
  2. Label 缺失:无统一标签体系,无法按类型/优先级筛选
  3. 填写无约束:Issue/PR 内容格式随意,审查时缺少关键信息

本规范的目标:让人类一眼识别项目+类型,让 Agent 可程序化遵循,让模板降低填写门槛。

26.2 规范设计

26.2.1 标题规范

规则:所有 Issue/PR 标题必须包含项目代号前缀。

类型 格式 示例
Issue [代号] type: 简述 [moz] bug: Mail API 500 when comment_type invalid
PR [代号] type(scope): 简述 [moz] impl(daemon): WikiGuideSection 注入

项目代号moz=moziplus_v2, quant=quant_live, vnpy=vnpy

type 清单bug / feat / impl / fix / docs / test / ci / refactor / chore

设计决策

  • 为什么用代号前缀而不是靠仓库隔离?— 团队成员同时在多仓库协作,通知列表(Mail、Gitea dashboard)混合展示时代号前缀提供即时辨识。仓库隔离解决不了跨仓库视图的辨识问题
  • PR 加 scope 是因为 PR 通常涉及具体模块(daemon / api / frontend),Issue 不需要

26.2.2 Label 体系

采用 type/* + priority/* 双命名空间,替代旧标签(bug/feature/improvement/security):

标签 色值 说明
type/bug #ee0701 Bug 修复
type/feat #84b6eb 新功能
type/impl #c5def5 按设计实现
type/docs #fbca04 文档
type/test #0e8a16 测试
type/ci #d4c5f9 CI/CD
type/refactor #ff6f00 重构
priority/P0 #b60205 紧急
priority/P1 #d93f0b
priority/P2 #fbca04
priority/P3 #cfd3d7

设计决策

  • type/ priority/ 命名空间而非扁平命名,避免标签膨胀时冲突,且在 Gitea UI 中按前缀分组显示
  • type 用暖色系(红/橙/黄),priority 用冷→热渐变(灰→蓝→黄→红),视觉上两类标签不混淆
  • ⚠️ 已知问题:type/impl(#c5def5) 与 priority/P3 色值相近。P3 已调整为灰色 #cfd3d7 以区分

26.2.3 Issue/PR 模板

Issue 模板3 种):bug.yml / feature.yml / test.yml

覆盖决策:只做最高频的 3 种类型(bug 报告、功能需求、测试任务),其余类型(docs/ci/refactor)频率低,走自由创建。每种模板包含描述、复现/方案、优先级字段。

PR 模板1 种):PULL_REQUEST_TEMPLATE.md

改动类型 checklist + 检查清单(标题格式、开发→安装目录同步、测试、设计文档更新)。

26.3 执行机制

规范通过四层路径保证执行,每层职责不同:

载体 职责 Token 成本
L1 TOOLS.mdAgent workspace 代号表 + 格式速查,Agent 静态可见 ~100
L2 GiteaConventionSectionpriority=55 每次 spawn 动态注入,提醒标题格式 ~80
L3 gitea-conventions SkillextraDirs 完整规范(标题/分支/commit/label),Agent 按需加载 按需
Gitea Issue/PR Template + Label(仓库级) 人类创建时表单引导,标签选择

L2 设计决策

  • GiteaConventionSection priority=55,排在 Constraints(50) 之后、Extension(60) 之前。标题规范属于约束类,但优先级低于安全/流程约束
  • 注入所有 handlerTask/Toolchain/Mail),因为任何 handler 都可能创建 Issue/PR
  • ⚠️ L1 文件在各 Agent workspace~/.openclaw/workspace-*/TOOLS.md),不在仓库管理。Agent workspace 变更不通过 PR

26.4 Label 迁移策略

旧标签(bug/feature/improvement/security/risk:high/priority:high)已由新体系替代:

  • 旧标签保留不动(不删除),避免历史 Issue 丢失标签信息
  • 新 Issue/PR 使用新标签type/* + priority/*
  • 当前不做批量迁移。如有需要可后续通过 Gitea API 批量替换

26.5 与其他章节的关系

章节 关系
§4.2 Issue 标签体系 §26.2.2 Label 设计在问题管理场景的具体应用(已随 PR #69 更新)
§4.5 标题规范 §26.2.1 标题规范的执行层摘要(已随 PR #69 新增)
§5 CI/CD 管道 CI 事件通过标题前缀 [CI] 做事件路由(见 §16 事件中枢)
§6 代码审查流程 PR Template 检查清单约束审查前置条件

26.6 实现记录

文件 改动
prompt_composer.py 新增 GiteaConventionSectionpriority=55
task_handler.py get_sections() 注册 GiteaConventionSection()
toolchain_handler.py get_sections() 注册 GiteaConventionSection()
mail_handler.py get_sections() 注册 GiteaConventionSection()
db.py COMMENT_TYPESaction_report(修 API 500 bug
.gitea/ISSUE_TEMPLATE/ bug.yml / feature.yml / test.yml
.gitea/PULL_REQUEST_TEMPLATE.md PR 检查清单模板
Gitea Labels 3 仓库各创建 11 个 Labeltype × 7 + priority × 4

PR #692026-06-14 合并。