diff --git a/docs/architecture/three-agent-handoff-workflow.md b/docs/architecture/three-agent-handoff-workflow.md new file mode 100644 index 0000000..a643bb9 --- /dev/null +++ b/docs/architecture/three-agent-handoff-workflow.md @@ -0,0 +1,160 @@ +# 三 Agent 交接闭环工作流(架构 / 开发 / 验收) + +> 适用仓库:`Weknora/knowledge-governance`(默认分支 `main`)。 +> 本文件是**交接契约**,不是介绍:它规定角色边界、状态机、版本口径与交接证据格式, +> 供三个独立会话(架构 / 开发 / 验收)在同一仓库上串行协作时逐条核对。 +> 依据:工单 `#6` 的「文档内容合同」8 条(正文=目标与范围,评论=证据,commit/tag=版本)。 + +## 0. 与工单合同条目的对应(便于独立验收逐条核对) + +| 合同条目 | 本文位置 | +|---|---| +| 1 三角色职责 | §1 | +| 2 main 单线、串行修改 | §2 | +| 3 六个状态名、标签互斥、blocked 要素 | §3 | +| 4 每轮可交接的修改链路(自测→commit→快照 tag→推送→核实→review) | §4、§5 | +| 5 不可变与回退规则 | §5 | +| 6 开发/验收交接模板 | §6 | +| 7 关单条件 | §7 | +| 8 安全与边界 | §8 | + +## 1. 三角色职责 + +| 角色 | 能做 | 不能做 | +|---|---|---| +| **架构** | 需求、设计、把目标/范围/验收标准写成工单;维护工单正文与验收矩阵;写设计文档 | 不写业务代码、不改测试/依赖/构建配置、不推送、不启动其他 Agent | +| **开发** | **唯一**可以改业务代码、测试、依赖、迁移、构建配置与项目文档的角色;自测、commit、打快照 tag、推送 main 与 tag | 不设置 `accepted`、不关单、不改验收标准、不部署 | +| **验收** | 独立复核差异与版本信息,逐项判定 PASS/FAIL/BLOCKED;通过后置 `accepted` 并关单 | 不修复代码(发现问题置 `rework` 并把问题列清)、不自行 `checkout`/`reset` | +| **prototype(UI)** | **仅**当工单涉及 UI 原型时介入,产出可打开的原型与交互证据 | 不参与非 UI 任务,不代替开发提交业务代码 | + +角色约束是**流程约束**,不是技术 ACL 隔离:本机共用 Gitea 管理员凭据,谁也拦不住谁越权, +所以每个角色都要靠"读回核实"而非"相信对方自述"来交付。 + +## 2. main 单线、单仓库串行 + +- 只使用 `main` 一条线:**不创建分支、不建 worktree、不走 PR、不做合并**(通用引擎规范里 PR 是可选姿态, + 本仓沿用"以直推为主"的口径)。 +- 同一仓库的代码修改**串行**:一个工单在主开发阶段只由一个开发会话持有。 +- **review 期间开发停止修改代码**:工单进入 `status:review` 后,除按 `rework` 要求修复外不再改动。 +- 切换现场前先取证:`git status --short --branch`、`git worktree list`、`git rev-parse origin/main`。 + 工作区不干净、或存在不属于本工单的改动时**不得混入**,安全处理不了就置 `status:blocked`。 +- 别人的 feature 分支与成果原样保留:不 `reset`、不 `stash`、不 cherry-pick、不清理旧分支/worktree/tag。 + +## 3. 工单状态机 + +六个完整状态名,**互斥**,同一工单最多一个状态标签: + +``` +status:ready → status:developing → status:review → status:accepted + ↑ │ + └── status:rework ─┘ status:blocked(可从中途进入,解除后回原状态) +``` + +| 标签 | 含义 | 进入条件(缺一不可) | +|---|---|---| +| `status:ready` | 设计完成、用户已批准开发 | 用户明确批准;范围与验收标准写明 | +| `status:developing` | 开发已领取并在实施 | 评论记录工单号、起始 SHA | +| `status:review` | 已交付待独立验收 | commit/tag/推送/远端核实/交接证据齐全 | +| `status:rework` | 验收失败 | 验收列出明确不通过项(不授权扩范围) | +| `status:accepted` | 指定版本通过验收待核实交付 | 验收矩阵逐项通过,结论绑定 tag + 完整 SHA | +| `status:blocked` | 受阻停手 | 有原因、处理人、解除条件、原状态;解除后**恢复原状态** | + +- `Closed` 是 **Issue 状态**,不是标签:通过验收后由验收移除状态标签并关闭。 +- 未批准的工单保持 Open 且**不打** `status:ready`。 +- 标签 ID **按仓库实时读取**(`GET /api/v1/repos/{owner}/{repo}/labels`),不跨仓库复用 ID。 +- ⚠️ Gitea 1.26.2 实测:`PATCH /issues/{n}` 带 `labels` 返回 201 **却不改任何标签**(静默无效); + 替换标签必须用 `PUT /issues/{n}/labels`,且每次写完 `GET /issues/{n}` **读回核验**。 +- ⚠️ 本仓历史遗留标签 `status/working`(`/` 而非 `:`)不符合本约定,遇到时应归一为 `status:developing`。 + +## 4. 快照 tag 与正式版本分线 + +**两种版本语义必须分开**,混用会让 CI 变红或让快照被误当发布。 + +| | 快照(开发交付用) | 正式版本(发布用) | +|---|---|---| +| 命名 | `snapshot/vX.Y.Z` | `vX.Y.Z` | +| 类型 | **注释 tag**(`git tag -a`) | **注释 tag**(`git tag -a`) | +| 批准 | 开发按工单直接创建,序号递增 | **用户单独批准** | +| 与 CHANGELOG 的关系 | 无要求;快照序号独立于构建显示版本,**不要求二者相等** | 必须同步写 `CHANGELOG.md` 顶部该版本段 | +| CI 门禁 | 不被当作正式版本 | 受版本一致性门禁约束 | +| 含义 | 代码快照,**不代表验收、不代表发布** | 发布事实,需 Release 等后续动作 | + +**为什么不能用 `vX.Y.Z` 当快照(实测结论,勿凭直觉改回)** + +- `make verify` → `scripts/check_changelog.py --tags`(`Makefile:27`、`scripts/ci_checks.py:254`)对每个 + `vX.Y.Z` tag 断言:tag 指到的提交里 `CHANGELOG.md` 顶部正式版本 == tag 版本,且必须是注释 tag + (`scripts/check_changelog.py:44-45,257-278`)。 +- CI 检出会把 `git ls-remote --tags origin` 落成 `.ci-remote-tags` 并 `fetch --tags`(`.gitea/scripts/ci_checkout.sh:122,127`); + `CI=true` 时拿不到清单直接判红 ⇒ **不带 CHANGELOG 段的正式 tag 会让 main 从此每次推送都红**。 +- 正式 tag 的匹配是 `^v(\d+\.\d+\.\d+)$`(`scripts/check_changelog.py:45`),`snapshot/v0.1.1` **不匹配**, + 因此不参与版本一致性判定;`scripts/version.py:119-151` 的发布证据也只认 `refs/tags/v<版本号>` 的注释 tag + 与 CHANGELOG 顶部一致 ⇒ 快照 tag 不会把构建身份变成 release。 +- 结论:快照一律用 `snapshot/vX.Y.Z`;要发正式版就单独走批准 + CHANGELOG 流程。 +- 修复后的下一快照序号递增(`snapshot/v0.1.2`),**不得移动已交付的快照 tag**。 + +## 5. 每轮可交接的完整链路 + 不可变性 + +一轮可交付的修改必须走完整条链,任何一步失败都要如实报告**实际状态**,不能假装完整交付: + +``` +自测(真实命令 + 真实结果) + → commit(信息含工单号;不用 Closes/Fixes/Resolves 等自动关单关键词) + → 创建递增的不可变快照 tag(注释 tag) + → 正常推送 main 与该 tag + → 远端核实(main 含结束 SHA;tag 解引用后指向结束 SHA) + → 工单评论完整交接证据 → status:review +``` + +- 开发的**正常 main 与快照 tag 推送无需逐次确认**;但**部署与正式发布不在授权内**。 +- 推送前必须先核查:目标名未被占用、推送是否会触发生产部署(`.gitea/workflows/` 里是否有 deploy 作业)。 + 本仓唯一工作流 `build.yml` 头部明确"只做 CI,不做 CD"(`on: push: branches: "**"`)⇒ 普通 CI 允许放行。 +- **保留旧提交与旧 tag**:不强推、不覆盖、不删除旧 tag、不改写历史。 +- 回退只能由开发执行 `git revert` 并创建**新的**版本(`revert` + 递增快照),旧提交与旧 tag 原样留着。 +- CI 绿 ≠ 已部署;`main` 不是自动可发布版本。 + +## 6. 交接与验收模板 + +### 6.1 开发交接(写进工单评论) + +| 字段 | 内容 | +|---|---| +| 工单 | 编号 + 标题 | +| 起始 SHA | 动手前的 `origin/main` 完整 SHA | +| 结束 SHA | 本次提交的完整 SHA | +| tag | 快照 tag 名 + 类型(注释)+ 解引用后的 SHA | +| 实现摘要 | 改了什么、为什么这么改 | +| 文件清单 | `git diff --stat` 的范围,逐条列出 | +| 测试命令与结果 | **真实执行的命令 + 真实输出**(不可只写"已测试") | +| 已知问题 | 未验证项、环境限制、待批事项 | +| 剩余完成项 | 明确列表;没有就写"无" | + +### 6.2 验收结论(写进工单评论) + +| 字段 | 内容 | +|---|---| +| 版本锚点 | tag + 完整 SHA(结论必须绑在这上面) | +| 逐项判定 | 合同/矩阵每项 `PASS` / `FAIL` / `BLOCKED`,FAIL 必须列明确问题 | +| 复验命令与结果 | 独立跑过的只读检查(如 `git diff --check`、远端 tag 读回)与真实输出 | +| 问题与结论 | 通过 ⇒ 下一步;不通过 ⇒ 置 `rework` 并把问题列清;无法验证 ⇒ 置 `blocked` | + +验收不得凭开发总结宣告通过;也不得自行 `checkout`/`reset` 去凑现场 —— 现场不对就要求开发准备。 + +## 7. 关单条件 + +同时满足才可关单: + +1. 验收矩阵全部通过; +2. 目标版本可追溯(tag + 完整 SHA 可读回); +3. 远端交付已核实(远端 main 含结束 SHA,tag 解引用一致); +4. 交接与验收文档齐全(含真实执行证据); +5. 无未解除的阻塞项。 + +**不需要** PR、不需要合并、**不默认需要部署**。 + +## 8. 安全与边界 + +- 不提交任何密钥/隐私:决定只用 secret 文件(仓库外、0600),日志与评论不出现秘密与正文原文。 +- 每个外部写操作(标签、评论、tag、推送)都**读回核实**;结果不明确时不得宣布成功。 +- 不使用自动关单关键词,不绕过独立验收。 +- 角色约束不是技术隔离;`main` 也不是自动可发布版本。 +- 每次执行开始先确认:仓库、路径、工单、当前状态、最新评论、授权;缺一项且无法查询就先问,不猜。 \ No newline at end of file