Compare commits

..

76 Commits

Author SHA1 Message Date
cfdaily b3707f1e62 revert: remove CI yml changes from lint PR
CI / lint (pull_request) Failing after 14m23s
CI / test (pull_request) Has been skipped
CI / notify-on-failure (pull_request) Failing after 14m58s
CI yml concurrency changes caused lint step to be skipped.
Lint PR should only contain source code formatting fixes.
2026-06-11 09:58:58 +08:00
cfdaily 2f1cb5c277 fix(lint): resolve all 37 flake8 issues
CI / lint (pull_request) Failing after 14m13s
CI / test (pull_request) Has been skipped
CI / notify-on-failure (pull_request) Failing after 12m7s
- Remove 7 unused imports (F401)
- Fix 4 f-strings without placeholders (F541)
- Fix indentation and blank line issues (E127/E302/E402)
- Remove trailing whitespace on 22 blank lines (W293)

Pure formatting changes, no logic modifications.
2026-06-11 08:55:05 +08:00
cfdaily 7b788809d8 Merge branch 'main' of http://192.168.2.154:3000/sanguo/sanguo_moziplus_v2 2026-06-11 08:52:04 +08:00
pangtong-fujunshi 4bf3d97a04 Merge pull request 'fix: 同步 4 个 post-PR#26 commit 到 Gitea' (#31) from fix/sync-to-gitea into main
Deploy / ci (push) Failing after 6s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 1s
2026-06-11 08:08:42 +08:00
cfdaily d98fae007f docs: §21 handler 注册后 E2E 验证报告
CI / lint (pull_request) Failing after 8s
CI / test (pull_request) Has been skipped
CI / notify-on-failure (pull_request) Successful in 3s
Mail/Toolchain 核心流程全部通过:
- Mail: inform auto-working → auto-done 
- Toolchain Issue 指派: webhook → Mail 
- Toolchain PR Review: webhook → Review 请求 → Review 结果 
- CI 失败重复 Mail 问题确认(org+repo webhook 双触发,已知)

Task review 路径待明天验证。
2026-06-11 08:08:20 +08:00
cfdaily 3c30a9b135 fix: task_handler SKILL_BASE_PATH 硬编码改为环境变量
与 bootstrap.py 保持一致,支持 MOZI_SKILL_PATH 环境变量覆盖。
默认值不变。
2026-06-11 08:08:20 +08:00
cfdaily 6bb0596e6d fix: dispatcher _legacy_on_complete 闭包内删除错误的重复 import
get_connection 已在文件顶部 L22 import(from src.blackboard.db),
闭包内 L258 错误地 from src.daemon.db import(模块不存在),
会触发 ImportError 被 try/except 吞掉导致 review verdict 逻辑不生效。

司马懿 review 发现。
2026-06-11 08:08:20 +08:00
cfdaily 25863634c2 fix: v3.0→HEAD review 修复 — handler 注册 + review verdict + skill 全文注入
基于庞统+司马懿背靠背 review,修复 6 个问题:

P0 致命:
- A1: _legacy_on_complete 补回 review verdict 处理(approved→done,非 approved→@mention assignee)
- A2: 添加 TaskTypeRegistry.register() 启动初始化(注册 Task/Mail/Toolchain handler)

P1 中等:
- B11-1: RoleSkillSection 从索引提示改为全文注入(对齐设计 §2.3 + BootstrapBuilder 行为)
- A8: retry prompt is_mail 硬编码改走 TaskTypeRegistry handler 判断

P2 低:
- _mail_* 4 个方法添加 DEPRECATED 注释
- ticker.py handler check_completion 代码块缩进对齐(28→24 空格)

测试:394 passed, 0 failed
Review reports: docs/design/review-v3-vs-head-{pangtong,simayi}.md
2026-06-11 08:08:20 +08:00
cfdaily 846fcbda5d docs: §21 handler 注册后 E2E 验证报告
Mail/Toolchain 核心流程全部通过:
- Mail: inform auto-working → auto-done 
- Toolchain Issue 指派: webhook → Mail 
- Toolchain PR Review: webhook → Review 请求 → Review 结果 
- CI 失败重复 Mail 问题确认(org+repo webhook 双触发,已知)

Task review 路径待明天验证。
2026-06-11 00:09:28 +08:00
cfdaily 83694adfea fix: task_handler SKILL_BASE_PATH 硬编码改为环境变量
与 bootstrap.py 保持一致,支持 MOZI_SKILL_PATH 环境变量覆盖。
默认值不变。
2026-06-10 23:46:32 +08:00
cfdaily 603af2a857 fix: dispatcher _legacy_on_complete 闭包内删除错误的重复 import
get_connection 已在文件顶部 L22 import(from src.blackboard.db),
闭包内 L258 错误地 from src.daemon.db import(模块不存在),
会触发 ImportError 被 try/except 吞掉导致 review verdict 逻辑不生效。

司马懿 review 发现。
2026-06-10 23:41:33 +08:00
cfdaily 36cee16679 fix: v3.0→HEAD review 修复 — handler 注册 + review verdict + skill 全文注入
基于庞统+司马懿背靠背 review,修复 6 个问题:

P0 致命:
- A1: _legacy_on_complete 补回 review verdict 处理(approved→done,非 approved→@mention assignee)
- A2: 添加 TaskTypeRegistry.register() 启动初始化(注册 Task/Mail/Toolchain handler)

P1 中等:
- B11-1: RoleSkillSection 从索引提示改为全文注入(对齐设计 §2.3 + BootstrapBuilder 行为)
- A8: retry prompt is_mail 硬编码改走 TaskTypeRegistry handler 判断

P2 低:
- _mail_* 4 个方法添加 DEPRECATED 注释
- ticker.py handler check_completion 代码块缩进对齐(28→24 空格)

测试:394 passed, 0 failed
Review reports: docs/design/review-v3-vs-head-{pangtong,simayi}.md
2026-06-10 23:37:46 +08:00
cfdaily fb31ce3752 fix: S1 handler display_name + S2 import 移顶部 + W1 注释
- S1: vp_name 硬编码字典 → handler.display_name 属性
- S2: ticker/spawner 中 TaskTypeRegistry 局部 import → 移文件顶部
- W1: TaskHandler executor verify 失败不调 on_failure 加注释说明
2026-06-10 23:37:46 +08:00
cfdaily 15fbc933ca feat: Step 5 引擎接入 + H1-H3/S3 修复 + 审计 D1/D2/D5 修复
引擎接入(dispatcher/spawner/ticker → handler 统一路由):
- dispatcher: guardrail/on_checks_passed/on_complete → handler 查询
- spawner: _build_prompt/_build_api_section → handler.build_prompt
- ticker: 虚拟项目扫描/assignee/claimed/review/幻觉门控 → handler 判断

Handler 缺陷修复:
- H1: _mark_task_status 加 3 次重试(防 DB 锁)
- H2: review @mention 加 comment_type='review'
- H3: review 非 approved 保持 review 状态(不标 working)
- S3: 通知链接改 Gitea(PR/Issue/Commit)

审计修复:
- D1: pre_spawn 返回值未检查 → 加 if not 抛 RuntimeError
- D2: PromptContext 缺 from_agent/mail_type → 从 must_haves 解析
- D5: _check_reply 查错表 → 恢复查 tasks 表找 in_reply_to

旧方法保留未删(deprecated),确认稳定后再清理。
2026-06-10 23:37:46 +08:00
cfdaily 5121b04d8c fix: S1-S3 review suggestions — type annotations unified, urllib replaces curl, rich notification content 2026-06-10 23:37:46 +08:00
cfdaily 65e8c4d461 feat: Step 2-4 Task/Mail/Toolchain handlers + PromptSections + BaseTaskHandler
- base_task_handler.py: 基类统一4步流程(crash→verify→mark→notify)
- task_handler.py: 5 PromptSections + 三信号验证 + review流程
- mail_handler.py: 3 PromptSections + inform/request区分 + 基类统一流程
- toolchain_handler.py: 3 PromptSections + 模板引擎渲染 + Mail API通知
- 背靠背设计-编码一致性检查通过(4严重已修/6轻微保留)
2026-06-10 23:37:46 +08:00
cfdaily b7136f4bf6 fix: S1-S4 建议项修复 — 类型标注精确化+BaseTaskHandler标注后续PR+token预算说明 2026-06-10 23:37:46 +08:00
cfdaily 02cb1610f1 fix: M1-M4 修复 Protocol 签名与设计文档对齐 + §14 去重 2026-06-10 23:37:46 +08:00
cfdaily f6a64e305b docs: 20-task-type-architecture.md v3.0 - §14-§18 五层架构+BaseTaskHandler+执行流程+决策记录 2026-06-10 23:37:46 +08:00
cfdaily 4bd109f09b feat: Step 1 — TaskTypeRegistry + PromptComposer 基础设施
- task_type_registry.py: TaskTypeHandler Protocol (10方法+2属性) + TaskTypeRegistry 注册表
- prompt_composer.py: PromptSection Protocol + PromptContext dataclass + PromptComposer 拼装器
- 零依赖,纯新增文件,不影响现有功能
2026-06-10 23:37:46 +08:00
cfdaily 9ef52049d1 docs: 20-task-type-architecture.md v2.1 - 修复 review M1-M3 必修项 2026-06-10 23:37:46 +08:00
cfdaily ad02cb8fef docs: 20-task-type-architecture.md v2.0 - 新增 §11-§13 PromptSection 模式 2026-06-10 23:37:46 +08:00
cfdaily 1e16f63be5 docs: add 20-task-type-architecture.md - TaskTypeRegistry + Handler 架构重构设计 2026-06-10 23:37:46 +08:00
cfdaily 3071c95629 docs(#13): merge #19 context layers into #13, delete standalone #19
§19 上下文四层改造方案(原独立文档 #19)合并到 #13 工具链设计文档末尾。
v3.1 → v3.3。两个专题本就是一个整体,分开维护增加认知负担。
2026-06-10 23:37:46 +08:00
cfdaily 29fb333c77 fix(frontend): resumed_from null→undefined 类型兼容
TypeScript: resumed_from 是 string|null,StatusButtons 期望 string|undefined。
用 ?? undefined 转换。
2026-06-10 23:37:46 +08:00
cfdaily 234c560522 fix(test): e2e test 在 collection 阶段跳过(不 import 安装目录)
根因: test_e2e_v27.py 的 skipif 只标记了函数级别,pytest collection 阶段
仍会 import 该文件,触发 sys.path.insert 指向安装目录的 spawner.py。
如果安装目录有 merge conflict 残留,整个 test job crash。

修复: 将 skipif 加入 pytestmark 级别,collection 阶段即跳过。
2026-06-10 23:37:46 +08:00
cfdaily 16a9783416 fix(frontend): V2Task 添加 resumed_from 字段
deploy 时 TypeScript 编译报 TS2339: Property 'resumed_from' does not exist on type 'V2Task'。
DB 表有此字段但 TS interface 遗漏。
2026-06-10 23:37:46 +08:00
cfdaily 52073fb955 fix(ci): deploy.yml 用 /tmp/ci-venv + 直接 pip install 替代 requirements.txt
仓库没有 requirements.txt,deploy workflow 每次 push 到 main 都报错。
改为与 ci.yml 一致的方式:/tmp/ci-venv + 直接 pip install 依赖。
2026-06-10 23:37:46 +08:00
cfdaily eaaf42b37d fix(lint): 修复 PR #14 引入的 lint 回退 (119→0)
PR #14 从旧分支复制文件导致回退了 PR #10 的 lint 修复。
修复内容:
- autoflake 移除未使用导入/变量
- autopep8 修复缩进/空格
- 手动修复 F821(pathlib→Path), F541(f-string), F841(未使用变量)
- 所有修复均通过 flake8 --max-line-length=120 --extend-ignore=E501 检查 (0 errors)
2026-06-10 23:37:46 +08:00
cfdaily c4b219892c docs(#08): update A13 revised - exit=0 always completed
Merge old A12/A13 into single A13 revised: trust exit_code=0
regardless of stdout/JSON output. Old logic caused inform Mail
infinite retry loop.
2026-06-10 23:37:46 +08:00
cfdaily 2478c425b0 fix(spawner): A13 exit=0 always completed, not agent_error
exit=0 means process exited normally. Trust the exit code regardless
of stdout/JSON output or task_status. Old logic misclassified inform
Mail completions as agent_error, causing infinite retry loops.

Includes test update: test_task_status_pending expects completed.
2026-06-10 23:37:46 +08:00
cfdaily e504e56ecc chore: simayi-approved changes - lint fixes, toolchain improvements, healthz
All changes reviewed and APPROVED in PR #12 (Review ID: 40):
- toolchain_routes: webhook repo/org format compat, content dedup (sha256), closed issue filter
- dispatcher: inform mail crash 误标 done 修复
- ticker: cleanup and improvements
- healthz endpoint
- conftest: integration/e2e deselect markers
- docs: design docs, test-guide updates
- various lint/whitespace fixes across 30 files
2026-06-10 23:37:46 +08:00
cfdaily d45ebe87e1 docs: #19 adopt simayi review suggestions (v1.1) 2026-06-10 23:37:46 +08:00
cfdaily 8fe0233d94 fix(spawner): crash cooldown分级 + inform mail crash误标done修复
- crashed outcome cooldown 60s(vs 其他 300s)
- import init_db
- whitespace/lint fixes
2026-06-10 23:37:46 +08:00
cfdaily 45c48c1ccf fix(ci): 修复notify竞态条件 - 用needs.result替代commit status查询
根因:notify-on-failure job 通过 commit status API 查询结果时,
自身的 pending status 会污染查询结果(竞态条件):
1. lint/test 都 success
2. notify 开始运行,自身状态 pending 写入 commit status
3. notify 查询 commit status → 看到 pending(自己的)≠ success
4. 误发 [CI] 失败 评论 + webhook 触发 Mail 通知

修复方案:
- 不再查询 commit status API
- 直接用 needs.lint.result 和 needs.test.result 判断
- 只有明确的 failure 才发通知
- 同时去掉 push 触发避免双倍运行
2026-06-10 23:37:46 +08:00
cfdaily d93ad989ab fix(ci): 去掉push触发避免双倍触发 + 修复notify误报
1. 触发器:去掉 push,只保留 pull_request(opened, synchronize)
   - 每次 push 到 PR 分支不再跑 2 次 CI
2. notify-on-failure:只有明确的 failure 状态才发通知
   - 之前:空状态/unknown/pending 都触发通知(误报根因)
   - 现在:只有 STATUS=failure 才发通知
3. venv 路径:统一用 /tmp/ci-venv-lint 和 /tmp/ci-venv-test
   - 避免 host 模式下与开发目录 .venv 冲突
2026-06-10 23:37:46 +08:00
cfdaily 1f4634feb9 fix: remove dead code config.get experience 2026-06-10 23:37:46 +08:00
cfdaily 33e8c68458 fix: resolve all flake8 lint errors (118 → 0) 2026-06-10 23:37:46 +08:00
jiangwei-infra 308c5a63bd fix(ci): install all test dependencies (fastapi, pydantic, pyyaml, etc.) 2026-06-10 23:37:07 +08:00
jiangwei-infra 3323bc76bd fix(ci): install pytest directly instead of editable mode 2026-06-10 23:37:07 +08:00
jiangwei-infra 9dd9e44a83 fix(ci): use pyproject.toml instead of missing requirements.txt 2026-06-10 23:37:07 +08:00
jiangwei-infra e7b6d4af45 fix(ci): use /tmp/ci-venv-* to avoid host .venv conflict 2026-06-10 23:37:07 +08:00
pangtong-fujunshi 09971b555a Merge pull request 'feat: Step 5 引擎接入 — dispatcher/spawner/ticker → handler 统一路由 + H1-H3/S3/D1/D2/D5 修复' (#26) from feat/step5-engine-integration into main
Deploy / ci (push) Failing after 7s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 1s
2026-06-10 22:39:03 +08:00
cfdaily 8085a71d9f auto-sync: 2026-06-09 11:57:58
CI / lint (push) Failing after 8s
CI / test (push) Has been skipped
CI / lint (pull_request) Failing after 6s
CI / notify-on-failure (push) Successful in 0s
CI / test (pull_request) Has been skipped
CI / notify-on-failure (pull_request) Successful in 3s
2026-06-09 11:57:58 +08:00
cfdaily 4840b68901 auto-sync: 2026-06-09 11:17:56 2026-06-09 11:17:56 +08:00
cfdaily 0111273811 auto-sync: 2026-06-09 11:16:05 2026-06-09 11:16:05 +08:00
cfdaily dd2572b8b8 auto-sync: 2026-06-09 11:15:09 2026-06-09 11:15:09 +08:00
cfdaily ce7c1e7108 auto-sync: 2026-06-09 11:13:34 2026-06-09 11:13:34 +08:00
cfdaily 68932f9be5 auto-sync: 2026-06-09 08:47:09 (catch-all) 2026-06-09 08:47:09 +08:00
cfdaily 6963faac83 auto-sync: 2026-06-09 08:46:58 2026-06-09 08:46:58 +08:00
cfdaily 5010ff7db1 auto-sync: 2026-06-09 08:30:45 2026-06-09 08:30:45 +08:00
cfdaily cf7e136330 auto-sync: 2026-06-09 08:06:43 2026-06-09 08:06:43 +08:00
cfdaily 795cfa81d1 auto-sync: 2026-06-09 07:46:23 2026-06-09 07:46:23 +08:00
cfdaily 3f1daa9f8d auto-sync: 2026-06-09 07:46:02 2026-06-09 07:46:02 +08:00
cfdaily 67a187aa0f auto-sync: 2026-06-09 00:45:50 2026-06-09 00:45:50 +08:00
cfdaily 4492a75e7e auto-sync: 2026-06-09 00:45:24 2026-06-09 00:45:24 +08:00
cfdaily 639fb3ecea auto-sync: 2026-06-09 00:44:21 2026-06-09 00:44:21 +08:00
cfdaily 60195f6250 auto-sync: 2026-06-09 00:38:45 2026-06-09 00:38:45 +08:00
cfdaily f00aeb96e9 auto-sync: 2026-06-09 00:24:51 2026-06-09 00:24:51 +08:00
cfdaily 96c8378a91 auto-sync: 2026-06-09 00:14:25 (catch-all) 2026-06-09 00:14:25 +08:00
cfdaily 632ca35681 auto-sync: 2026-06-09 00:14:14 2026-06-09 00:14:14 +08:00
cfdaily 041f54e699 auto-sync: 2026-06-08 23:39:15 2026-06-08 23:39:15 +08:00
cfdaily 81cca26adb auto-sync: 2026-06-08 23:38:59 (catch-all) 2026-06-08 23:38:59 +08:00
cfdaily 29438a5789 auto-sync: 2026-06-08 23:38:34 (catch-all) 2026-06-08 23:38:34 +08:00
cfdaily 5d83747e99 auto-sync: 2026-06-08 23:37:35 (catch-all) 2026-06-08 23:37:35 +08:00
cfdaily 339519a062 auto-sync: 2026-06-08 23:37:25 2026-06-08 23:37:25 +08:00
cfdaily 12f03e48a4 auto-sync: 2026-06-08 23:23:43 2026-06-08 23:23:43 +08:00
cfdaily 473ae73230 auto-sync: 2026-06-08 23:22:36 2026-06-08 23:22:36 +08:00
cfdaily 34335a6487 auto-sync: 2026-06-08 23:21:31 2026-06-08 23:21:31 +08:00
cfdaily b2ace1b6a7 auto-sync: 2026-06-08 23:20:47 2026-06-08 23:20:47 +08:00
cfdaily 55fc25d9a6 auto-sync: 2026-06-08 23:19:23 2026-06-08 23:19:23 +08:00
cfdaily 0b7bb288f9 auto-sync: 2026-06-08 22:58:35 2026-06-08 22:58:35 +08:00
cfdaily b5d26da914 auto-sync: 2026-06-08 22:26:47 2026-06-08 22:26:47 +08:00
cfdaily f32991ddee auto-sync: 2026-06-08 22:11:11 2026-06-08 22:11:11 +08:00
cfdaily 0546825642 auto-sync: 2026-06-08 22:04:07 2026-06-08 22:04:07 +08:00
cfdaily f0a673ff20 auto-sync: 2026-06-08 21:59:26 2026-06-08 21:59:26 +08:00
13 changed files with 1154 additions and 61 deletions
+102
View File
@@ -0,0 +1,102 @@
# §21. Handler 注册后 E2E 验证
> 日期:2026-06-11
> 状态:已完成 ✅
> 目标:验证 Task 五层架构重构(Step 2-5+ review 修复后,Mail/Toolchain 路径端到端工作
## 前置条件
- Daemon 版本:commit 83694ad(含 handler 注册 + import 修复 + SKILL_BASE_PATH 修复)
- Handler 注册日志:
```
Registered task type handler: task (virtual_project=None)
Registered task type handler: mail (virtual_project=_mail)
Registered task type handler: toolchain (virtual_project=_toolchain)
```
- Gitea org webhook (ID=28):姜维启用,事件订阅含 issues/pull_request/pull_request_review 等 16 个事件
- 测试仓库:sanguo/sanguo_moziplus_v2
## 验证结果
### 一、Mail Handler(✅ 全部通过)
| # | 步骤 | 验证点 | 结果 | Mail ID |
|---|------|--------|------|---------|
| 1 | 发 inform 邮件给 zhangfei-dev | ticker 发现 `_mail` 虚拟项目 | ✅ `handler auto-working` | mail-1781106713261 |
| 2 | zhangfei-dev 回复 | handler verify (inform_auto) → done | ✅ `verify passed (inform_auto), marked done` | — |
| 3 | 回复邮件给 pangtong | handler auto-working + done | ✅ `verify passed (inform_auto), marked done` | mail-1781106736388 |
**关键验证**
- ✅ `virtual_projects()` 返回 `["_mail", "_toolchain"]`(注册前为空)
- ✅ handler `pre_spawn` (auto-working) 生效,不是旧的 `_mail_auto_working`
- ✅ guardrail 跳过 `_mail``is_handler_task=True`
- ✅ inform 类型自动标 donerequest 类型检查回复
### 二、Toolchain — Issue 指派(✅ 通过)
| # | 步骤 | 验证点 | 结果 | Mail ID |
|---|------|--------|------|---------|
| 1 | 创建 Issue #28assignee=zhangfei-dev | webhook 触发 + Mail 通知 | ✅ | mail-1781107087549 |
**Webhook 路径**Gitea → org webhook → `POST /webhook/gitea` → 签名验证 → `_handle_issues` → `_send_mail(zhangfei-dev, ...)`
**注意**Issue #27 创建时 webhook 未启用,未触发。Issue #28 创建时 webhook 已启用,正常触发。
### 三、Toolchain — PR Review(✅ 通过)
| # | 步骤 | 验证点 | 结果 | Mail ID |
|---|------|--------|------|---------|
| 1 | 创建 PR #30 | webhook 触发 + Review 请求 Mail | ✅ | mail-1781107538823 |
| 2 | simayi-challenger 提交 COMMENT review | Review 结果通知 PR 作者 | ✅ `Review 通过 ✓` | mail-1781107650433 |
**Webhook 路径**
- PR opened: Gitea → `_handle_pull_request` → `_send_mail(simayi-challenger, "Review 请求")`
- PR review: Gitea → `_handle_pull_request_review` → `_send_mail(pangtong-fujunshi, "Review 通过 ✓")`
### 四、CI 失败评论(⚠️ 触发但重复)
| # | 步骤 | 验证点 | 结果 | Mail ID |
|---|------|--------|------|---------|
| 1 | push 空 commit → CI lint 失败 | CI 失败通知 | ✅ 但收到 2 封重复 Mail | mail-1781107563991, mail-1781107560933 |
**已知问题**:和上次 E2E(§18)相同——org webhook + repo webhook 双触发。上次已加去重机制(delivery UUID + content sha256),但 CI 失败场景似乎仍触发 2 封。**非新问题,待姜维统一 org/repo webhook 后解决。**
### 五、负面测试(❌ 未执行)
| 步骤 | 说明 | 状态 |
|------|------|------|
| REQUEST_CHANGES review | review 驳回通知 PR 作者 | 未测(仲达提交的是 COMMENT 而非 REQUEST_CHANGES |
| 已关闭 Issue CI 评论 | closed issue 不触发 Mail | 未测 |
| 部署失败 Issue | 双收件人通知 | 未测 |
| 幂等测试 | 同 delivery ID 重发 | §18 已验证,未重测 |
## 阻塞/问题记录
### 已解决
| 问题 | 说明 |
|------|------|
| Org webhook 事件列表被 Gitea API 重置 | 姜维修复:PATCH webhook 只传 active:true 会重置 events,必须带完整事件列表 |
| PR Review 无法用 PR 作者 token 提交 | Gitea 不允许 self-review,请仲达用 simayi token 提交 |
### 遗留
| 问题 | 严重度 | 说明 |
|------|--------|------|
| CI 失败 Mail 重复 | 🟡 | org webhook + repo webhook 双触发,§18 已记录 |
| REQUEST_CHANGES 未验证 | 🟢 | 下次 E2E 补测 |
## 测试清理
- ✅ Issue #27、#28 已关闭
- ✅ PR #29、#30 已关闭
- ✅ 分支 `test/e2e-1781107119`、`test/e2e-pr-1781107530` 已删除
- ✅ 本地切回 main 分支
## 结论
**Handler 注册后 Mail 和 Toolchain 核心流程端到端验证通过。** 关键修复(handler 注册、review verdict、SKILL_BASE_PATH)均已生效。
下一步:
- Task review 路径 E2E(明天,需要普通任务 → executor → review → verdict → done
- CI 失败重复 Mail 根治(需姜维统一 org/repo webhook
+224
View File
@@ -0,0 +1,224 @@
# v3.0 vs HEAD 背靠背 Review — 庞统
**日期**: 2026-06-11
**范围**: v3.0 tag → HEAD6 commits, Step 2-5 Task 五层架构重构)
**对比**: `git diff v3.0..HEAD` + 安装目录代码验证
---
## Part A: v3.0 逻辑丢失检查
### 方法论
v3.0 → HEAD 的重构将 `_mail_*` 硬编码逻辑统一为 handler 架构(TaskTypeRegistry + BaseTaskHandler)。核心变更:
- dispatcher.py: `_mail_on_checks_passed` / `_mail_on_complete``_handler_on_checks_passed` / `_handler_on_complete`
- spawner.py: `_build_mail_prompt` → handler.build_prompt
- ticker.py: `_mail_check_reply` → handler.check_completion, `_mail` 硬编码 → `TaskTypeRegistry.virtual_projects()`
### 检查结果
| # | 文件 | v3.0 逻辑 | 当前状态 | 严重度 | 说明 |
|---|------|----------|---------|--------|------|
| 1 | dispatcher.py | `_legacy_on_complete` 中 review verdict 处理(approved→done, 非 approved→@mention assignee | **缺失** | 🔴 | 新版 `_legacy_on_complete``_is_review=True` 时只有 crash rollback**没有 verdict 判断逻辑**。review agent 完成后任务永远不会从 review→done。**仅影响非 handler 项目(_general**。handler 项目(_mail/_toolchain)的 review 由 TaskHandler.post_complete 正确处理 |
| 2 | dispatcher.py | `_mail_auto_working` / `_mail_auto_complete` / `_mail_revert_to_pending` 方法 | 保留但主流程不再调用 | 🟢 | 方法体仍存在(标记为 deprecated),主流程改走 handler.pre_spawn / handler.post_complete。正常的重构 |
| 3 | dispatcher.py | spawn 失败回退 `working→pending` | **逻辑改进** | 🟢 | v3.0 用 `_mail_revert_to_pending`(只处理 _mail),新版用通用 DB 操作处理所有 handler 项目 |
| 4 | spawner.py | `_build_mail_prompt` 精简模板 | **替换为 handler.build_prompt** | 🟢 | MailHandler 使用 PromptSection 组装,功能更完整 |
| 5 | spawner.py | `_build_api_section` 中 mail 直接 done | **替换为 handler.target_success_status** | 🟢 | 等价实现 |
| 6 | ticker.py | `_mail` 硬编码虚拟项目 | **替换为 TaskTypeRegistry.virtual_projects()** | 🟢 | 正常重构,可扩展 |
| 7 | ticker.py | `_mail_check_reply` 兜底(超时检查) | **替换为 handler.check_completion** | 🟢 | 等价实现,缩进正确 |
| 8 | ticker.py | `_dispatch_reviews` 跳过 `_mail` | **替换为 handler 检查** | 🟢 | 等价 |
### 🔴 严重问题 #1 详解
**位置**: `dispatcher.py` L250-260 `_legacy_on_complete`
**v3.0 逻辑**(已删除):
```python
if _is_review:
if _task_db and outcome in ("completed", "session_revived"):
# 读 verdict
if verdict == "approved":
_dispatcher._mark_task_status(_task_db, _task_id, "done")
else:
# @mention assignee + 保持 review
bb.add_comment(_task_id, "daemon", f"@{assignee} 审查结论: {verdict_str}")
```
**当前逻辑**:
```python
def _legacy_on_complete(aid, outcome):
if outcome in ROLLBACK_CURRENT_AGENT_OUTCOMES and _task_db:
_dispatcher._rollback_current_agent(_task_db, _task_id, aid)
if not _is_review: # ← review 时什么都不做
_dispatcher._task_auto_complete(_task_id, _task_db)
```
**影响**: `_dispatch_reviews` (ticker.py:1307) 对非 handler 项目会 dispatch review agent。review agent 完成后走 `_legacy_on_complete`,但 `_is_review=True` 时逻辑为空。任务永远停在 `review` 状态。
**修复方案**: 在 `_legacy_on_complete` 中补充 review verdict 处理逻辑,或让非 handler 项目也走 TaskHandler(注册 `_general` 到 TaskTypeRegistry)。
---
## Part B: 专题 01-13 设计编码一致性
### 专题 01: 四相循环(不参考实现,只检查设计遗漏)
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | §3.3 Spawn Prompt 框架(任务+约束+API+准则+完成标准) | ✅ BootstrapBuilder + PromptSection 实现 | ✅ | |
| 2 | §3.4 @mention 通知机制 | ✅ `_process_mentions` + `mention_queue` | ✅ | |
| 3 | §4 庞统 Review 机制(三问) | ✅ review agent + verdict 处理 | ✅ | |
**设计遗漏**: 无明显遗漏。
### 专题 02: Main Session + Delegation
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | 3.1 投递到 Main Session | ✅ `use_main_session=True` 参数 | ✅ | |
| 2 | 3.2 Delegationsubagent-delegation skill | ✅ 外部 skill,不在此代码库 | ✅ | |
| 3 | 3.3 续杯机制 | ✅ `use_main_session=True` + session 复用 | ✅ | |
| 4 | 4.1 投递消息格式 | ✅ dispatcher 构建 | ✅ | |
| 5 | 4.3 消息优先级与中断策略 | ❌ 无优先级队列 | ⚠️ | 设计描述了优先级但未实现,非关键 |
| 6 | 4.4 Subagent 背压控制 | ❌ 无显式背压 | ⚠️ | 靠 counter 间接控制 |
### 专题 03: Prompt 进化
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | 3.1 广播认领模板改写 | ✅ PromptSection 组装 | ✅ | |
| 2 | P4 群体智能(Boids | ✅ agent 自主决策 | ✅ | 设计原则,非具体代码 |
| 3 | P6 反静默降级 | ❌ 无 scope reduction detection 自动机制 | ⚠️ | 设计原则,未自动实现 |
| 4 | P7 经验闭环 | ❌ 无 IMPROVE 阶段自动触发 | ⚠️ | P4 级待实现 |
### 专题 04: 黑板协作模型
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | 3.1 assignee 降级为显示字段,路由走 @mention | 🟡 assignee 仍做直接路由 | ⚠️ | router.py L160-166 仍有 assignee 快速路径。设计说 Phase 1 双轨并行,当前停在 Phase 1。未迁移到 Phase 2 |
| 2 | 3.2 @mention 语义增强(mention_queue + comment_type | ✅ 已实现 | ✅ | |
| 3 | 3.3 多人协作模式(co_assignees | ❌ 无 co_assignees 字段 | ❌ | 数据库无此列 |
| 4 | 3.4 信息关联模型(output↔comment link | ❌ 无关联字段 | ❌ | outputs 表无 comment_id 列 |
| 5 | 3.5 层级查询 API | ✅ parent_task 支持 | ✅ | |
**总结**: 3.3 和 3.4 设计了但未实现。3.1 停在 Phase 1。
### 专题 05: 上下文四层架构
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | L0 铁律层 | ✅ 通过 workspace 文件注入 | ✅ | |
| 2 | L1 角色层 | ✅ SOUL.md / IDENTITY.md | ✅ | |
| 3 | L2 引擎注入层 | ✅ BootstrapBuilder | ✅ | |
| 4 | L3 被动参考层 | ❌ 无 _inject_wiki_knowledge | ❌ | wiki 知识注入未实现 |
### 专题 06: PM2 Crash 恢复
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | 4.1 总体流程(_startup_recover | ✅ ticker.py:1614 | ✅ | |
| 2 | 4.2 claimed 状态恢复 | ✅ | ✅ | |
| 3 | 4.2 working 状态恢复 | ✅ `_recover_working_task` | ✅ | |
| 4 | 4.2 review 状态恢复 | ✅ `_recover_review_task` | ✅ | |
| 5 | 设计提到 7 个恢复方法 | 🟡 只看到 2 个公开方法 | ⚠️ | 可能在内部逻辑中覆盖,需详细检查 |
### 专题 07: Spawner Acquire-First
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | Phase 0: Pre-acquire 修复 | ✅ L499-512 | ✅ | |
| 2 | Phase 1: Counter acquire | ✅ L516-521 | ✅ | |
| 3 | Phase 2: Session check | ✅ L523-568 | ✅ | |
| 4 | Phase 2.5: 假死修复 | ✅ L557-568 | ✅ | |
| 5 | O1: lock PID 死 + running 假死 | ✅ | ✅ | |
| 6 | O4: revive 清理 lock 文件 | ✅ | ✅ | |
### 专题 08: Classify Outcome 优化
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | A0-A17 判定树 | ✅ `_classify_outcome` 方法 | ✅ | |
| 2 | A9 api_error 特殊路径 | ✅ api_retry_count | ✅ | |
| 3 | A14-A17 可恢复 retry + cooldown 60s | ✅ cooldown_seconds + set_cooldown | ✅ | |
| 4 | Gateway Watchdog | ✅ 外部脚本 | ✅ | |
| 5 | Registry 逻辑删除 | ✅ | ✅ | |
### 专题 09: Rebuttal + Goal Gate
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | 2.1 Rebuttal 自动化(review 非 approved → @mention assignee | ✅ task_handler.py handle_review_complete + ticker.py _rebuttal_on_complete | ✅ | |
| 2 | 2.1 防止无限循环(max 2 轮) | ✅ RebuttalManager.MAX_ROUNDS | ✅ | |
| 3 | 2.2 目标一致性 Gate | ❌ 无 goal gate 自动检查 | ⚠️ | 设计为 Agent 端行为,非 Daemon 侧 |
| 4 | _task_on_complete 改动(design §2.1 代码改动) | 🟡 已移到 handler | ✅ | 重构后的等价位置 |
### 专题 10: T3 需求探索 + 黑板展示
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | A2: 需求探索过程写黑板 comments | ✅ 后端支持 comment_type | ✅ | |
| 2 | A3: TaskModal 实时刷新 | ✅ SSE comment_added/checkpoint_resolved | ✅ | |
| 3 | D1: 砍掉 AI 摘要 | ✅ 黑板直投前端 | ✅ | |
| 4 | D2: SSE 只做通知 | ✅ 前端按需拉数据 | ✅ | |
### 专题 11: 上下文四层重设计
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | L2 操作规范型 6 个 skill 全文注入 | ❌ BootstrapBuilder 只注入通用 prompt,无 skill 全文注入 | ❌ | 设计 §2.3 要求将 6 个操作规范型 skillblackboard-executor, code-review 等)全文注入 L2bootstrap.py 无此逻辑 |
| 2 | L3 _inject_wiki_knowledge | ❌ 完全未实现 | ❌ | |
| 3 | review_protocols/ 目录 | ❌ 目录不存在 | ❌ | |
| 4 | 2.3 提到的 handoff.schema.json | ❌ 不存在 | ❌ | |
**总结**: 专题 11 大部分 L2/L3 改造未实现。BootstrapBuilder 做了基础框架但缺少 skill 注入和知识注入。
### 专题 12: Pipeline 设计
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | §3 Pipeline 注册表(pipeline 字段) | ❌ 无 pipeline 数据结构 | ❌ | |
| 2 | §4 路由逻辑更新(task_type 路由) | ❌ router.py 无 task_type 路由 | ❌ | |
| 3 | §8 Pipeline 引擎 + PipelineRegistry | ❌ 不存在 | ❌ | |
| 4 | §8.2 状态流转校验 | ❌ 无 flow_rules | ❌ | |
| 5 | §9 实施路线标记为 "待实现" | — | — | 设计文档本身就标记为 TODO |
**总结**: Pipeline 整个设计未实施。设计文档 §9 自身标记为待实现。
### 专题 13: 工具链开发工作流(不参考实现,只检查设计遗漏)
| # | 设计描述 | 代码状态 | 一致性 | 说明 |
|---|---------|---------|--------|------|
| 1 | §16 工具链事件中枢 | ✅ toolchain_routes.py + toolchain_handler.py | ✅ | |
| 2 | Gitea webhook 处理 | ✅ 5 模板 + 去重 | ✅ | |
| 3 | CI 前缀 [CI] | ✅ | ✅ | |
| 4 | §5 CI/CD 管道设计 | 🟡 Gitea Actions 为主,非 Daemon 侧 | ✅ | |
**设计遗漏**: 无明显遗漏。
---
## 汇总
### 🔴 严重(需修复)
| # | 问题 | 影响 |
|---|------|------|
| A1 | `_legacy_on_complete` review verdict 处理丢失 | 非 handler 项目(_general)的 review agent 完成后任务永远停在 review 状态 |
### 🟡 中等(设计-代码不一致,可后续处理)
| # | 专题 | 设计描述 | 实际状态 |
|---|------|---------|---------|
| B4-1 | 04 黑板协作 | 3.1 assignee 降级 Phase 2 | 停在 Phase 1 |
| B4-3 | 04 黑板协作 | 3.3 co_assignees 多人协作 | 未实现 |
| B4-4 | 04 黑板协作 | 3.4 output↔comment 关联 | 未实现 |
| B5-4 | 05 上下文层 | L3 wiki 知识注入 | 未实现 |
| B11-1 | 11 上下文重设计 | L2 操作规范型 skill 全文注入 | 未实现 |
| B11-2 | 11 上下文重设计 | handoff.schema.json | 未实现 |
| B11-3 | 11 上下文重设计 | review_protocols/ 目录 | 未实现 |
| B12 | 12 Pipeline | 整个 Pipeline 引擎 | 未实现(设计自标 TODO) |
### 🟢 正常(重构等价或设计已标记待实现)
- _mail_* 方法 deprecated 但保留(平滑迁移)
- handler 架构统一替代硬编码(等价实现)
- 专题 01/02/03/06/07/08/09/10/13 无严重不一致
+707
View File
@@ -0,0 +1,707 @@
# v3.0 vs HEAD 背靠背 Review — 司马懿
> **日期**: 2026-06-10 (v2)
> **范围**: v3.0 tag → HEAD6 commits, +1584/-134 行, 9 个文件)
> **方法**: `git diff v3.0..HEAD` 逐文件逐行比对 + v3.0 源码 `git show v3.0:` 回溯验证
> **独立判断**: 不参考庞统 review,独立产出后比对
---
## 总览
v3.0 → HEAD 的核心改动是 **Step 2-5 五层架构重构**
| 层 | 新增/改动 | 说明 |
|---|---------|------|
| Protocol + Registry | `task_type_registry.py`(已有,未改) | `TaskTypeHandler` Protocol + `TaskTypeRegistry` |
| 基类 | `base_task_handler.py`(新增 +183 | `BaseTaskHandler` — crash/verify/mark/notify 统一流程 |
| Handler × 3 | `task_handler.py`+378)、`mail_handler.py`+210)、`toolchain_handler.py`+277 | 各自实现 `build_prompt` / `verify_completion` / `post_complete` |
| 引擎接入 | `dispatcher.py`-95/+58)、`spawner.py`+38)、`ticker.py`+31/-27 | `_mail_*` 硬编码 → `TaskTypeRegistry` 查表 |
| 设计文档 | `step5-impact-analysis.md`+324)、`step5-audit-report.md`(+74) | 影响分析 + 双重审计 |
**核心结论**:架构方向正确,但 **handler 注册初始化缺失导致所有 handler 路径为死代码**,实际运行仍走 `_legacy_on_complete` 旧路径。旧路径中 review verdict 处理被删除,造成 **非 handler 项目的 review 流程失效**
---
## Part A: v3.0 逻辑丢失检查
### 方法论
逐文件追踪 v3.0 中每个 `_mail` / `_task` / `project_id == "_mail"` 分支,验证 HEAD 中是否存在等价实现。分三层检查:
1. **功能等价**:新代码是否完整覆盖旧逻辑
2. **路径可达**:新代码是否会被实际执行(handler 注册?legacy fallback?)
3. **行为一致**:边界条件、异常处理是否等价
### 检查结果
#### A1 🔴 致命:dispatcher.py — review verdict 处理丢失
**v3.0 逻辑**`dispatcher.py` L253-308 `_task_on_complete`):
```python
if _is_review:
if _task_db and outcome in ("completed", "session_revived"):
# 读 verdict
review = conn.execute(
"SELECT verdict FROM reviews WHERE task_id=? ORDER BY created_at DESC LIMIT 1",
(_task_id,)).fetchone()
if review and review["verdict"] == "approved":
_dispatcher._mark_task_status(_task_db, _task_id, "done")
else:
# 非 approved → @mention assignee + 保持 review
bb.add_comment(_task_id, "daemon", f"@{assignee} 审查结论: {verdict_str}")
```
**HEAD 逻辑**`dispatcher.py` L246-258 `_legacy_on_complete`):
```python
def _legacy_on_complete(aid, outcome):
if outcome in ROLLBACK_CURRENT_AGENT_OUTCOMES and _task_db:
_dispatcher._rollback_current_agent(_task_db, _task_id, aid)
if not _is_review: # ← review 时整个 if 被跳过
_dispatcher._task_auto_complete(_task_id, _task_db)
```
**分析**
- `_legacy_on_complete``_is_review=True` 时**什么也不做**——无 verdict 读取、无 done 标记、无 @mention 通知
- `TaskHandler.handle_review_complete()` 方法有完整 verdict 处理,但 handler 未注册(见 A2),此代码不可达
- **影响**:所有非 handler 项目(`_general` 等)的 review agent 完成后,任务永远停在 `review` 状态
**补充**rebuttal 路径不受影响——`_rebuttal_on_complete``ticker.py` L756-790 独立定义,直接读 verdict 并处理,不经过 `_legacy_on_complete`
---
#### A2 🔴 致命:Handler 注册初始化缺失
**证据**
```bash
$ grep -rn "TaskTypeRegistry.register" src/
# 零结果
```
`TaskTypeRegistry.register()` 在整个代码库中**从未被调用**。`TaskHandler` / `MailHandler` / `ToolchainHandler` 类已定义但从未实例化和注册。
**后果链**
1. `TaskTypeRegistry.get_by_project()` 永远返回 `None`
2. 所有 `if handler:` 分支不进入 → 走 `else` / fallback 路径
3. `TaskTypeRegistry.virtual_projects()` 返回空列表 → `_mail` / `_toolchain` 不被 ticker 自动发现
**各路径受影响分析**
| 路径 | dispatcher | spawner | ticker | 实际走什么 |
|------|-----------|---------|--------|----------|
| Mail `_mail` | `handler=None``_legacy_on_complete` | `handler=None` → 旧 `_build_mail_prompt` | `virtual_projects()` 空 → **_mail 不被 tick** | 旧路径(无 handler),但 **ticker 不扫描 _mail** |
| Task `_general` | `handler=None``_legacy_on_complete` | `handler=None` → BootstrapBuilder | 不涉及 handler | 旧路径,但 review 处理被删(A1) |
| Toolchain `_toolchain` | N/A | N/A | `virtual_projects()` 空 → **_toolchain 不被 tick** | **完全不可达** |
**⚠️ A2 导致 ticker 不再扫描 `_mail` 虚拟项目**,这是 v3.0 有、HEAD 丢失的行为——v3.0 中 `_mail` 硬编码在 ticker L218-229HEAD 改为 `TaskTypeRegistry.virtual_projects()` 但注册为空。
**需要添加的初始化代码**(缺失):
```python
# 应在 server.py 或 bootstrap.py 的启动流程中
from src.daemon.task_handler import TaskHandler
from src.daemon.mail_handler import MailHandler
from src.daemon.toolchain_handler import ToolchainHandler
TaskTypeRegistry.register(TaskHandler())
TaskTypeRegistry.register(MailHandler())
TaskTypeRegistry.register(ToolchainHandler())
```
---
#### A3 🟡 中等:dispatcher.py — 旧 `_mail_*` 方法成为死代码
**v3.0**`_mail_auto_working` / `_mail_auto_complete` / `_mail_check_reply` / `_mail_revert_to_pending``dispatch()` 主流程调用。
**HEAD**:这些方法仍保留在 dispatcher.py 中(L628-860),但主流程已改走 handler 路径。由于 handler 未注册,主流程走 `_legacy_on_complete`(无 handler 分支),也不调用这些方法。
**结论**:方法体保留但无外部调用者,属于死代码。不影响当前运行(因为 `_legacy_on_complete` 有独立的 executor 逻辑),但增加维护混淆。
---
#### A4 🟢 低:dispatcher.py — spawn 失败回退等价
**v3.0**`self._mail_revert_to_pending(task.id, db_path)` — 调独立方法。
**HEAD**:内联代码(L309-327),`BEGIN IMMEDIATE` + 状态检查 + `UPDATE ... SET status='pending'`
**等价**:新版逻辑更通用(不限于 `_mail`,任何 handler 项目都可回退)。
---
#### A5 🟢 低:dispatcher.py — `_legacy_dispatch` 路径 handler 化
**v3.0**`is_mail_legacy = project_id.get("project_id") == "_mail"`
**HEAD**`handler_legacy = TaskTypeRegistry.get_by_project(project_id_legacy)`
**等价**`handler_legacy` 为 None 时跳过 pre_spawn,与 v3.0 中 `is_mail_legacy=False` 行为一致。`_legacy_dispatch` 本身仅在 `router=None` 时触发,当前配置不会进入。
---
#### A6 🟢 低:spawner.py — prompt 构建双路径
**v3.0**`if project_id == "_mail": return self._build_mail_prompt(...)` → 走 BootstrapBuilder。
**HEAD**`handler = TaskTypeRegistry.get_by_project(project_id)``if handler: return handler.build_prompt(ctx)` → else 走 BootstrapBuilder。
**分析**
- handler 未注册时,等价于 v3.0(走 BootstrapBuilder
- handler 注册后,Task/Mail/Toolchain 走新 PromptSection 路径
- **注意**:新旧路径的 Skill 注入策略不同——旧路径(BootstrapBuilder**全文注入** Skill,新路径(RoleSkillSection)只给**索引+引导语**。这可能导致 handler 注册后 Agent 行为变化
---
#### A7 🟢 低:spawner.py — `_build_api_section` success_status
**v3.0**`success_status = '"done"' if project_id == "_mail" else '"review"'`
**HEAD**`success_status = '"done"' if handler.target_success_status == "done" else '"review"'`
**等价**handler 未注册时走 else 分支 → `'"review"'`,与 v3.0 非 mail 项目一致。
---
#### A8 🟡 中等:spawner.py — retry prompt 仍用硬编码
**v3.0**`is_mail = project_id == "_mail"` → 用 `MAIL_RETRY_PROMPT` 模板。
**HEAD**:同样 `is_mail = project_id == "_mail"` 硬编码(L1128),未改走 handler。
**影响**:不影响功能(retry prompt 正确),但与设计文档 §6 不一致。属于 Step 5 未覆盖的遗留点。
---
#### A9 🟢 低:ticker.py — 虚拟项目扫描
**v3.0**:硬编码 `_mail` 扫描。
**HEAD**`TaskTypeRegistry.virtual_projects()` 循环。
**分析**:逻辑正确,但注册为空时 `_mail` 不被扫描(见 A2)。注册后自动发现 `_mail` + `_toolchain`,比 v3.0 更可扩展。
---
#### A10 🟢 低:ticker.py — assignee 清空条件
**v3.0**`if self._current_project_id == "_mail":` → 不清空 assignee。
**HEAD**`handler = TaskTypeRegistry.get_by_project(...); if handler:` → 不清空。
**等价**handler 未注册时,非 handler 项目正常清空 assignee。
---
#### A11 🟢 低:ticker.py — 跳过 claimed 状态
**v3.0**`if project_id == "_mail":` → 跳过 claimed,直接 working。
**HEAD**`handler = TaskTypeRegistry.get_by_project(project_id); if handler:` → 跳过。
**等价**
---
#### A12 🟢 低:ticker.py — review dispatch 跳过
**v3.0**`if project_id == "_mail": return []`
**HEAD**`handler = TaskTypeRegistry.get_by_project(project_id); if handler: return []`
**等价**
---
#### A13 🟢 低:ticker.py — 超时检查幻觉门控
**v3.0**
```python
if self._current_project_id == "_mail":
has_reply = self._mail_check_reply(task.id, db_path)
if has_reply:
... # mark done
```
**HEAD**
```python
handler = TaskTypeRegistry.get_by_project(self._current_project_id)
if handler and handler.check_completion(task.id, db_path):
... # mark done
```
**等价**`MailHandler.check_completion` 内部调 `_check_reply`,查询语义与 v3.0 的 `_mail_check_reply` 完全一致(`SELECT id FROM tasks WHERE id != ? AND must_haves LIKE ?`)。
**缩进问题**HEAD L1483 `if handler and handler.check_completion(...):` 后续 body 缩进 5 级(28 空格),与同级代码不一致。不影响运行,但增加维护混淆。
---
#### A14 🟢 低:ticker.py — startup recovery 虚拟项目列表
**v3.0**`for virtual_id in ("_general", "_mail"):`
**HEAD**`virtual_ids = ["_general"] + TaskTypeRegistry.virtual_projects()`
**等价**:注册为空时只有 `_general`,注册后自动包含 `_mail` + `_toolchain`
---
### Part A 汇总
| 严重度 | 数量 | 项目 |
|--------|------|------|
| 🔴 致命 | 2 | A1 review verdict 丢失, A2 handler 未注册 |
| 🟡 中等 | 2 | A3 死代码未清理, A8 retry prompt 硬编码 |
| 🟢 低 | 10 | A4~A7, A9~A14 |
**A1+A2 联合根因分析**
设计意图是 handler 注册后 review 走 `TaskHandler.post_complete``handle_review_complete`。但注册代码缺失导致:
1. 所有项目走 `_legacy_on_complete`(旧路径)
2. 旧路径中 review 处理被删除(信任 handler 会处理)
3. review agent 完成后无任何后续动作
**同时**ticker 不再扫描 `_mail` 虚拟项目(原来硬编码扫描),`_mail` 项目的 pending 任务无人处理。
---
## Part B: 13 个重点专题设计-编码一致性
逐专题检查设计文档描述与 HEAD 代码的一致性。标记:
- ✅ 一致
- ⚠️ 设计已标注未实施/Phase N(不算差异)
- ❌ 设计承诺但代码不一致
- 🟡 部分一致
---
### B1: 专题 01 四相循环
**设计文档**`01-four-phase-loop.md` — PRD Phase 1~4 完整实现方案
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B1-1 | §3.3 Spawn Prompt 框架(任务+约束+API+准则+完成标准) | BootstrapBuilder + PromptSection 实现 | ✅ |
| B1-2 | §3.4 @mention 通知机制 | `_process_mentions` + `mention_queue` | ✅ |
| B1-3 | §4 Review 机制(verdict → done/notify | `TaskHandler.handle_review_complete`handler 未注册)+ `_rebuttal_on_complete`ticker 独立) | ⚠️ handler 路径不可达,但 rebuttal 路径完整 |
---
### B2: 专题 02 Main Session + Delegation
**设计文档**`02-main-session-delegation.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B2-1 | §3.1 投递到 Main Session | `use_main_session=True` 参数 | ✅ |
| B2-2 | §3.3 续杯机制 | `use_main_session=True` + session 复用 | ✅ |
| B2-3 | §4.3 消息优先级与中断策略 | 无优先级队列 | ⚠️ 设计描述但未标注 Phase |
| B2-4 | §4.4 Subagent 背压控制 | 无显式背压,靠 counter 间接控制 | ⚠️ |
---
### B3: 专题 03 Prompt 进化
**设计文档**`03-prompt-evolution.md` — 从 SOP 到任务式指挥
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B3-1 | §3.1 广播认领模板改写 | PromptSection 组装(新路径)+ BootstrapBuilder(旧路径) | ✅ |
| B3-2 | P6 反静默降级 | 无 `scope-reduction-detection` 自动机制 | ⚠️ 设计原则,未强制实施 |
| B3-3 | P7 经验闭环 | 无 IMPROVE 阶段自动触发 | ⚠️ |
---
### B4: 专题 04 黑板协作模型
**设计文档**`04-blackboard-collaboration-model.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B4-1 | §3.1 assignee 降级为显示字段,路由走 @mention | `router.py` L160-166 仍有 assignee 快速路径 | ⚠️ 设计说 Phase 1 双轨并行,Phase 2 废弃。当前停在 Phase 1 |
| B4-2 | §3.2 @mention 语义增强 | `mention_queue` + `comment_type` 已实现 | ✅ |
| B4-3 | §3.3 多人协作 `co_assignees` | 数据库无此字段 | ⚠️ Phase 3 |
| B4-4 | §3.4 output↔comment 关联 | 无关联字段 | ⚠️ Phase 2 |
| B4-5 | §3.5 层级查询 API | `parent_task` 支持 | ✅ |
---
### B5: 专题 05 上下文四层架构
**设计文档**`05-context-layers.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B5-1 | L0 铁律层 | workspace 文件注入(SOUL.md/IDENTITY.md 等) | ✅ |
| B5-2 | L1 角色层 | SOUL.md / IDENTITY.md | ✅ |
| B5-3 | L2 引擎注入层 | BootstrapBuilder 实现 | ✅ |
| B5-4 | L3 被动参考层(wiki knowledge | 无 `_inject_wiki_knowledge` | ⚠️ 设计标注为 Phase 2 |
---
### B6: 专题 06 PM2 Crash 恢复
**设计文档**`06-pm2-crash-recovery.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B6-1 | §4.1 总体流程 `_startup_recover` | ticker.py L1614 有启动恢复 | ✅ |
| B6-2 | §4.2 claimed 状态恢复 | ✅ | ✅ |
| B6-3 | §4.2 working 状态恢复 `_recover_working_task` | ✅ | ✅ |
| B6-4 | §4.2 review 状态恢复 `_recover_review_task` | ✅ | ✅ |
---
### B7: 专题 07 Spawner Acquire-First
**设计文档**`07-spawner-acquire-first.md`#07.1 已实施, #07.2 已实施
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B7-1 | Phase 0 Pre-acquire 修复 | spawner.py L499-512 | ✅ |
| B7-2 | Phase 1 Counter acquire | spawner.py L516-521 | ✅ |
| B7-3 | Phase 2 Session check | spawner.py L523-568 | ✅ |
| B7-4 | Phase 2.5 假死修复 | spawner.py L557-568 | ✅ |
---
### B8: 专题 08 Classify Outcome 优化
**设计文档**`08-classify-outcome-optimization.md` — 已实施 ✅
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B8-1 | A0-A17 判定树 | `_classify_outcome` 方法 | ✅ |
| B8-2 | A9 api_error 特殊路径 | `api_retry_count` | ✅ |
| B8-3 | A14-A17 可恢复 retry + cooldown 60s | `cooldown_seconds` + `set_cooldown` | ✅ |
---
### B9: 专题 09 Rebuttal + Goal Gate
**设计文档**`09-rebuttal-and-goal-gate.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B9-1 | §2.1 Rebuttal 自动化(review 非 approved → @mention assignee | `review.py RebuttalManager` + `ticker.py _rebuttal_on_complete` | ✅ |
| B9-2 | §2.1 防止无限循环(max 2 轮) | `RebuttalManager.MAX_ROUNDS = 2` | ✅ |
| B9-3 | §2.2 目标一致性 Gate | 无自动 goal gate 检查 | ⚠️ 设计为 Agent 端行为,非 Daemon 侧 |
---
### B10: 专题 10 T3 需求探索 + 黑板展示
**设计文档**`10-t3-requirement-exploration-and-blackboard-display.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B10-1 | A2 需求探索过程写黑板 comments | 后端支持 `comment_type` | ✅ |
| B10-2 | A3 TaskModal 实时刷新 | SSE `comment_added` / `checkpoint_resolved` | ✅ |
| B10-3 | D1 砍掉 AI 摘要 | 黑板直投前端 | ✅ |
| B10-4 | D2 SSE 只做通知 | 前端按需拉数据 | ✅ |
---
### B11: 专题 11 上下文四层重设计
**设计文档**`11-context-layers-redesign.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B11-1 | §2.3 L2 操作规范型 6 个 Skill 全文注入 | `BootstrapBuilder``ROLE_SKILL_MAP` + `_read_skill` 全文注入 ✅;`task_handler.py RoleSkillSection` 只给索引+引导语 ⚠️ | 🟡 **双路径并存**,策略矛盾 |
| B11-2 | §2.3 `handoff.schema.json` | 不存在 | ⚠️ Phase 3 |
| B11-3 | §2.3 `review_protocols/` 目录 | 不存在,但 `review-quality` Skill 文件存在 | ⚠️ 设计文档 §三归属表已改归类为 L3 Skill |
| B11-4 | §6 Phase 3 Step 6-8 BootstrapBuilder 改造 | 已完成(ROLE_SKILL_MAP + _read_skill | ✅ |
| B11-5 | §2.3 token 预算 ~600 tokens | bootstrap.py 有 warn 但不截断 | 🟡 有告警无硬限制 |
**B11 关键发现**:新旧路径的 Skill 注入策略矛盾——
- 旧路径(BootstrapBuilder):**全文注入** Skill`_read_skill` 读文件全文)
- 新路径(RoleSkillSection):**只给索引**"请用 read 工具读取 SKILL.md"
- 设计文档 §2.3 要求 "A 类 Skill 全文注入"
- handler 注册后会从旧路径切换到新路径,导致 **Skill 从全文注入降级为索引提示**
这是一个 **隐性回归**:注册 handler 后 Agent 获取的操作规范信息量大幅减少。
---
### B12: 专题 12 Pipeline 设计
**设计文档**`12-pipeline-design.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B12-1 | §3 Pipeline 注册表 | 不存在 | ⚠️ 设计 §9 标注 Phase 2 |
| B12-2 | §4 路由逻辑 task_type | router.py 无 task_type 路由 | ⚪ |
| B12-3 | §8 PipelineRegistry | 不存在 | ⚪ |
| B12-4 | §10.1 task_type 默认值改 None | `blackboard_routes.py` 已为 Nonev3.0 已修) | ⚪ **已实施** |
| B12-5 | §10.2 广播计数器修正 | `_broadcast_tracker` + `BroadcastRound` 已实现 | ⚪ **已实施** |
**B12 结论**:Pipeline 主体未实施符合设计路线图(Phase 2),但 §10 Phase 1 的两个 bug fix 明确标注为"立做"却未执行。
---
### B13: 专题 13 工具链与开发工作流
**设计文档**`13-toolchain-and-dev-workflow.md`
| # | 设计要求 | 代码现状 | 判定 |
|---|---------|---------|------|
| B13-1 | §16 工具链事件中枢 | `toolchain_handler.py` + `toolchain_templates.py` | ✅ |
| B13-2 | Gitea webhook 处理 | 5 模板 + 去重 | ✅ |
| B13-3 | CI 前缀 `[CI]` | ✅ | ✅ |
---
### Part B 汇总
| 判定 | 数量 | 主要项目 |
|------|------|---------|
| ✅ 一致 | 21 | B1-1, B1-2, B2-1/2, B3-1, B4-2/5, B5-1/2/3, B6-1~4, B7-1~4, B8-1~3, B9-1/2, B10-1~4, B11-4, B13-1~3 |
| 🟡 部分一致 | 3 | B11-1 双路径策略矛盾, B11-5 token 预算无硬限制 |
| ⚠️ 设计标注未实施 | 10 | B1-3handler 不可达), B2-3/4, B3-2/3, B4-1/3/4, B5-4, B9-3, B11-2/3 |
| ❌ 设计承诺未交付 | 0 | — |
---
## Step 5 审计报告偏差项验证
`step5-audit-report.md` 列出 6 项偏差(D1-D6)。逐项验证 HEAD 代码:
| # | 审计描述 | HEAD 实际状态 | 判定 |
|---|---------|-------------|------|
| D1 | pre_spawn 返回值未检查 | **已修复**`if not _handler.pre_spawn(...): raise RuntimeError("handler_pre_spawn_failed")` | ✅ 已修 |
| D2 | PromptContext 缺少 from_agent/mail_type | **已修复**spawner L289-296 从 must_haves JSON 提取 | ✅ 已修 |
| D3 | inform outcome 白名单缺失 | 未修复。但影响极小——CRASH_OUTCOMES 由基类处理,剩余异常 outcome 罕见 | 🟢 可接受 |
| D4 | retry prompt 仍用 `is_mail` 硬编码 | **未修复**spawner L1128 仍硬编码 `is_mail = project_id == "_mail"` | 🟡 遗留 |
| D5 | _check_reply 语义差异 | **已修复**MailHandler._check_reply 用 `SELECT id FROM tasks WHERE id != ? AND must_haves LIKE ?`,与 v3.0 一致 | ✅ 已修 |
| D6 | 标 done 重试机制 | **已修复**`BaseTaskHandler._mark_task_status` 有 3 次重试 | ✅ 已修 |
**结论**D1/D2/D5/D6 已在后续 commit 修复,D3 可接受,D4 是遗留项。
---
## 与庞统 Review 的背靠背比对
| 维度 | 司马懿 | 庞统 | 差异分析 |
|------|--------|------|---------|
| **致命问题** | A1 review verdict 丢失 + A2 handler 未注册 | 仅 #1 review verdict 丢失 | **关键差异**:庞统未将 handler 未注册列为致命问题。庞统认为 `_legacy_on_complete` 仍可运行所以只关注 review 路径。但我认为 **ticker 不再扫描 `_mail`** 是 v3.0 有、HEAD 丢失的行为,这比 review 路径更严重——Mail 系统完全停止工作 |
| **_mail tick 丢失** | 明确指出 A2 导致 ticker 不扫描 `_mail` | 未提及 | 庞统漏检了 `virtual_projects()` 返回空时 `_mail` 不被 tick 的后果 |
| **Skill 注入降级** | B11-1 发现新旧路径策略矛盾 | 未提及 | 庞统未分析 handler 注册后 Skill 注入策略的变化 |
| **D1/D2/D5 修复状态** | 验证了审计报告的修复项 | 未验证 | 庞统的 review 早于修复 commit |
| **专题覆盖范围** | 13 个全覆盖 | 13 个全覆盖 | 一致 |
| **D4 retry 硬编码** | A8 独立发现 | B4 独立发现 | 独立确认 |
---
## 建议优先级
| 优先级 | 项目 | 说明 |
|--------|------|------|
| **P0** | A2 handler 注册初始化 | 添加 `TaskTypeRegistry.register()` 启动代码。**这是所有 handler 路径的前提** |
| **P0** | A1 review verdict 处理 | P0 修复后自然恢复(`TaskHandler.handle_review_complete` 生效)。**但如果不想立即注册 handler**,需先在 `_legacy_on_complete` 中恢复 review verdict 逻辑作为 interim fix |
| **P1** | B11-1 Skill 注入策略对齐 | 注册 handler 后 RoleSkillSection 只给索引——需确认这是有意降级还是遗漏。如果是遗漏,RoleSkillSection 应全文注入 |
| **P1** | A8 retry prompt handler 化 | spawner L1128 `is_mail` 硬编码改走 handler |
| **P2** | A3 死代码清理 | `_mail_auto_working` / `_mail_auto_complete` 等方法在 handler 注册后确认不再需要再删除 |
| ~~P2~~ | ~~B12-4/5~~ | ~~已验证 v3.0 tag 时已修复,不需要再改~~ |
| **P3** | ticker.py L1483 缩进对齐 | 风格问题 |
---
## Cross-Check:庞统 Review 逐项验证
对庞统 `review-v3-vs-head-pangtong.md` 的每个发现,独立验证:**是不是真问题?根因对不对?修复方案对不对?**
---
### Part A 逐项验证
#### 庞统 #1 🔴 review verdict 丢失
**庞统判定**:致命。"仅影响非 handler 项目(_general"。
**我的验证**
1. **是致命问题** ✅ —— `_legacy_on_complete``_is_review=True` 时确实什么都不做。
2. **"仅影响非 handler 项目" — 表述不准确**。实际情况更复杂:
- `_general` 项目确实受影响(走 `_legacy_on_complete`
-`_mail` / `_toolchain` 不受影响,**不是因为"handler 正确处理"**,而是因为它们**根本不走 review 流程**ticker 中 `_dispatch_reviews` 对 handler 项目 `return []`
- 庞统说"handler 项目(_mail/_toolchain)的 review 由 TaskHandler.post_complete 正确处理"——**这个说法有误导性**。TaskHandler 不是 `_mail`/`_toolchain` 的 handler,它们各自的 handlerMailHandler/ToolchainHandler)没有 `handle_review_complete` 方法。它们不走 review 是因为设计上就不走。
3. **庞统的修复方案有隐藏缺陷**。庞统说"让非 handler 项目也走 TaskHandler(注册 `_general` 到 TaskTypeRegistry"。但 `TaskTypeRegistry.get_by_project()` 匹配的是 `handler.virtual_project`,而 TaskHandler 的 `virtual_project = None`。所以:
- `get_by_project("_general")` → 遍历所有 handler,检查 `h.virtual_project == "_general"` → TaskHandler 的 `virtual_project``None`**不匹配** → 返回 `None`
- 即使注册了 TaskHandler`_general` 项目仍然走 `_legacy_on_complete`
- 庞统的修复方案需要**额外改 TaskHandler.virtual_project 或 registry 匹配逻辑**,但他没指出这一点
**结论**:问题是真的,严重度判定正确。但影响范围描述和修复方案都不完整。
---
#### 庞统 #2 🟢 旧 `_mail_*` 方法保留
**庞统判定**:正常重构,方法体保留标记为 deprecated。
**我的验证**
1. **方法体确实保留** ✅(dispatcher.py L628-860
2. **但"标记为 deprecated"不对**——代码中没有 `@deprecated` 装饰器或注释。这些方法就是安静地躺在那里,没有任何标记告诉维护者"别用了"
3. **我标 🟡 中等而非 🟢**的原因:无 deprecated 标记 + 主流程不再调用 = 未来维护者容易误用
**结论**:问题不大,但庞统多给了信息("标记为 deprecated")——代码中实际没有标记。
---
#### 庞统 #3 🟢 spawn 失败回退
**庞统判定**:逻辑改进。
**我的验证**:✅ 确认等价,新版更通用。
---
#### 庞统 #4-5 🟢 spawner prompt/api_section
**庞统判定**:等价实现。
**我的验证**:✅ 确认等价。
---
#### 庞统 #6 🟢 ticker `_mail` → `virtual_projects()`
**庞统判定**:正常重构,可扩展。
**我的验证****这是庞统最大的漏检**。
庞统只看了代码方向(硬编码 → 注册表),**没有检查注册表是否为空**。
实际运行时 `TaskTypeRegistry.virtual_projects()` 返回空列表 → `_mail` 不被 ticker 扫描。这是一个 **v3.0 有、HEAD 丢失的行为**——v3.0 中 `_mail` 硬编码在 ticker L218-229HEAD 中完全消失。
后果:所有 Mail 任务的 pending → claimed → working 流程中断,整个飞鸽传书系统停止工作。
这不是"正常重构",是**致命回归**。
---
#### 庞统 #7-8 🟢 ticker check_reply / dispatch_reviews
**庞统判定**:等价实现。
**我的验证**:✅ 确认等价。但 #7 说"缩进正确"——实际 ticker.py L1483 有缩进不一致(28 空格 vs 同级 24 空格),不影响运行但增加维护混淆。
---
### Part B 逐专题验证
#### 专题 01-03:无分歧
庞统的检查和我的结论一致。设计原则未强制实施属于正常。
---
#### 专题 04:庞统更严格
庞统把 B4-3co_assignees)和 B4-4output↔comment)标 ❌,我标 ⚪(Phase 2/3)。
庞统的判定更严格——"设计了但没实现就是不一致" vs 我的"设计自身标注了 Phase,未实施是预期的"。两种视角都有道理,**不算错误**。
---
#### 专题 05:判定标准差异
庞统把 B5-4(L3 wiki 知识注入)标 ❌。我标 ⚪(Phase 2)。
同专题 04,判定标准差异。
---
#### 专题 06:庞统更细致
庞统多了 B6-5"设计提到 7 个恢复方法只看到 2 个公开方法"——这是一个合理的疑问,我没有提出。
---
#### 专题 07-10:无分歧
---
#### 专题 11:庞统全标 ❌ 是错的
庞统 B11-1 说"BootstrapBuilder 只注入通用 prompt,无 skill 全文注入"。
**我验证了代码**
```python
# bootstrap.py L29
ROLE_SKILL_MAP = {
"executor": "blackboard-executor",
"reviewer": "blackboard-reviewer",
...
}
# bootstrap.py L68-72
skill_name = self.ROLE_SKILL_MAP.get(role)
if skill_name:
skill_content = self._read_skill(skill_name) # 读全文
if skill_content:
sections.append(skill_content)
```
**BootstrapBuilder 有 Skill 全文注入**。庞统说"无 skill 全文注入"与代码不符。他可能只看了 `task_handler.py` 的 RoleSkillSection(确实只给索引),没有看 `bootstrap.py` 的旧路径。
**实际情况**:双路径并存。旧路径(BootstrapBuilder)全文注入,新路径(RoleSkillSection)只给索引。handler 注册后从旧路径切换到新路径,Skill 信息量降级。这才是真正的问题。
---
#### 专题 12:我之前的 B12-4/5 判定有误
我在 Part B 中说"B12-4 task_type 默认值仍为 `\"coding\"`"和"B12-5 广播计数器 retry_count 不递增"是 Phase 1 承诺未交付。
**cross-check 时我重新验证了代码**
- **B12-4**`blackboard_routes.py` L138 已是 `body.get("task_type", None)`**默认值已经是 None**。v3.0 tag 中也是 None。设计文档 §10.1 的 bug fix 可能在 v3.0 之前就修了,或者设计文档基于旧版本写的。**不是问题**,我之前的判定有误。
- **B12-5**`ticker.py``_broadcast_tracker` + `BroadcastRound` + `round_number >= 3` 升级庞统的机制已实现。`mark_mention_retry``retry_count = retry_count + 1`。设计 §10.2 描述的问题已在 v3.0 或更早修复。**不是问题**,我之前的判定有误。
庞统对专题 12 的判定("设计文档 §9 自身标记为待实现")比我准确。
**修正我的报告**Part B 中 B12-4 和 B12-5 应从 ❌ 改为 ⚪(设计自标 Phase 2,主体未实施是预期的)。
---
#### 专题 13:无分歧
---
### 庞统未引用 Step 5 审计报告
庞统的 review 完全没引用 `step5-audit-report.md`v3.0..HEAD diff 中新增的文件)。这意味着 D1/D2/D5 的修复状态未经庞统验证。我逐项验证了 D1/D2/D5 **已修复**D4 **未修复**retry 硬编码),D3 **可接受**D6 **已修复**
---
### 庞统漏检的额外行为回归
handler 未注册还导致一个庞统完全没提到的问题:
**guardrail 回归**。v3.0 中 dispatcher L127-128
```python
is_mail = project_config.get("project_id") == "_mail" if project_config else False
if self.guardrails and not is_mail:
```
HEAD dispatcher L128-131
```python
handler = TaskTypeRegistry.get_by_project(project_config.get("project_id", "") ...)
is_handler_task = handler is not None
if self.guardrails and not is_handler_task:
```
handler 未注册 → `is_handler_task = False`**`_mail` 项目也要过 guardrail 检查了**。v3.0 中 `_mail` 是跳过 guardrail 的。这可能导致某些 Mail 任务被 guardrail 拦截。
---
### Cross-Check 总结
| 维度 | 庞统 review 质量 |
|------|-----------------|
| **致命问题发现** | 发现 A1 ✅,漏检 A2handler 注册 + ticker 不可达 + guardrail 回归)❌ |
| **根因分析** | A1 根因正确。修复方案不完整(没指出 TaskHandler.virtual_project=None 导致注册也匹配不到 `_general` |
| **Part B 专题覆盖** | 13/13 全覆盖 ✅ |
| **Part B 事实准确性** | B11 "无 skill 全文注入"与代码不符 ❌。B12 比我准确 ✅ |
| **Part B 多给信息** | #2 说"标记为 deprecated"但代码无标记 ⚠️ |
| **Part B 更严格处** | B04-3/4 标 ❌(合理),B06-5 恢复方法数量疑问(合理) |
| **审计报告验证** | 未引用,未验证 D1-D6 修复状态 |
| **遗漏的行为回归** | guardrail 对 `_mail` 的回归 |
**我的自我修正**:B12-4/5 判定有误,应改为 ⚪。v3.0 tag 时这两个问题已修复,设计文档描述的是更早期的问题。
---
*— 司马懿 仲达,质量总监 🗡️*
+21 -21
View File
@@ -10,7 +10,7 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Optional
from src.daemon.prompt_composer import PromptContext, PromptComposer, PromptSection
from src.daemon.prompt_composer import PromptContext, PromptSection
from src.blackboard.db import get_connection
logger = logging.getLogger("moziplus-v2.handler")
@@ -28,46 +28,46 @@ class VerifyResult:
class BaseTaskHandler:
"""所有 task type handler 的基类。
职责:L2 引擎注入层的业务逻辑——prompt 构建、完成验证、状态标记。
不管:进程生命周期、exit 分类、重试决策(这些归 spawner)。
"""
# crash 类 outcome(进程级异常,需要 rollback)
CRASH_OUTCOMES = frozenset({
"crashed", "compact_failed", "process_crash",
"session_stuck", "compact_hanging",
})
task_type: str = ""
virtual_project: Optional[str] = None
display_name: str = "" # 中文展示名(ticker 扫描日志用)
# === 子类必须实现 ===
def build_prompt(self, context: PromptContext) -> str:
"""构建 L2 prompt(通过 PromptComposer 拼 section)。子类实现。"""
raise NotImplementedError
def verify_completion(self, task_id: str, db_path: Path) -> VerifyResult:
"""验证任务完成质量。每个 handler 自己的验证逻辑。子类实现。"""
raise NotImplementedError
def target_success_status(self) -> str:
"""验证通过后的目标状态。task='review', mail/toolchain='done'"""
return "review"
def get_sections(self) -> list[PromptSection]:
"""返回此 handler 的 prompt section 列表。子类实现。"""
return []
# === 基类提供统一流程 ===
def pre_spawn(self, task_id: str, db_path: Path) -> bool:
"""spawn 前业务准备。默认 True。
mail/toolchain override 为 auto_working。"""
return True
def post_complete(self, task_id: str, agent_id: str,
outcome: str, db_path: Path) -> None:
"""spawn 完成后的业务处理。统一 4 步流程:
@@ -80,10 +80,10 @@ class BaseTaskHandler:
if outcome in self.CRASH_OUTCOMES:
self._rollback_current_agent(db_path, task_id, agent_id)
return
# 2. verify
result = self.verify_completion(task_id, db_path)
# 3. mark
if result.passed:
self._mark_task_status(db_path, task_id, self.target_success_status())
@@ -92,20 +92,20 @@ class BaseTaskHandler:
else:
# 4. notify
self.on_failure(task_id, agent_id, db_path, result)
def on_failure(self, task_id: str, agent_id: str,
db_path: Path, verify: VerifyResult) -> None:
"""验证失败处理。默认:标 failed。子类可 override。"""
self._mark_task_status(db_path, task_id, "failed")
logger.info("Task %s: verify failed (%s), marked failed",
task_id, verify.reason)
task_id, verify.reason)
def check_completion(self, task_id: str, db_path: Path) -> bool:
"""ticker 级别的完成检查。默认:False。"""
return False
# === 内部工具方法 ===
def _rollback_current_agent(self, db_path: Path, task_id: str, agent_id: str) -> None:
"""crash 后回退 current_agent → assignee,避免 exclude_current 卡死。
从 dispatcher._rollback_current_agent 迁移。"""
@@ -126,7 +126,7 @@ class BaseTaskHandler:
except Exception as e:
logger.warning("Task %s: failed to rollback current_agent: %s",
task_id, e)
def _mark_task_status(self, db_path: Path, task_id: str, status: str) -> None:
"""更新任务状态 + 写审计事件(带 3 次重试,防 SQLite DB 锁)。"""
for attempt in range(3):
@@ -157,7 +157,7 @@ class BaseTaskHandler:
logger.warning("Handler: mark %s%s attempt %d failed: %s",
task_id, status, attempt + 1, e)
logger.error("Handler: mark %s%s all 3 attempts failed", task_id, status)
def _auto_mark_working(self, task_id: str, db_path: Path) -> bool:
"""pending → workingmail/toolchain 通用)。"""
try:
+45 -3
View File
@@ -252,9 +252,47 @@ class Dispatcher:
if outcome in ROLLBACK_CURRENT_AGENT_OUTCOMES and _task_db:
_dispatcher._rollback_current_agent(
_task_db, _task_id, aid)
if not _is_review:
_dispatcher._task_auto_complete(
_task_id, _task_db)
if _is_review:
if _task_db and outcome in ("completed", "session_revived"):
from src.blackboard.blackboard import Blackboard
# get_connection 已在文件顶部 L22 import
rconn = get_connection(_task_db)
try:
review_row = rconn.execute(
"SELECT verdict, reviewer, comment FROM reviews "
"WHERE task_id=? ORDER BY created_at DESC LIMIT 1",
(_task_id,)).fetchone()
finally:
rconn.close()
if review_row and review_row["verdict"] == "approved":
_dispatcher._mark_task_status(
_task_db, _task_id, "done")
logger.info(
"Legacy %s: review approved, marked done", _task_id)
else:
verdict_str = review_row["verdict"] if review_row else "未知"
tconn = get_connection(_task_db)
try:
t_row = tconn.execute(
"SELECT assignee FROM tasks WHERE id=?",
(_task_id,)).fetchone()
finally:
tconn.close()
if t_row and t_row["assignee"]:
bb = Blackboard(str(_task_db))
bb.add_comment(
_task_id, "daemon",
f"@{t_row['assignee']} review 未通过 "
f"(verdict={verdict_str}): "
f"{review_row['comment'] if review_row else ''}",
comment_type="review")
logger.info(
"Legacy %s: review not approved (%s), "
"@mentioned assignee",
_task_id, verdict_str)
else:
_dispatcher._task_auto_complete(_task_id, _task_db)
except Exception as e:
logger.error(
"Legacy %s: on_complete error: %s", _task_id, e)
@@ -625,6 +663,7 @@ class Dispatcher:
# ── Mail 信封/载荷分离辅助方法 ──
# DEPRECATED: Step 5 handler 架构已替代此方法,保留仅供平滑过渡,确认稳定后删除。
def _mail_auto_working(self, task_id: str, db_path: Path) -> bool:
"""Mail 任务:系统自动标 workingspawn 前)
@@ -662,6 +701,7 @@ class Dispatcher:
logger.error("Mail %s: failed to mark working: %s", task_id, e)
return False
# DEPRECATED: Step 5 handler 架构已替代此方法,保留仅供平滑过渡,确认稳定后删除。
def _mail_revert_to_pending(self, task_id: str, db_path: Path) -> None:
"""Mail spawn 失败时回退 working → pending,避免永久死锁"""
try:
@@ -691,6 +731,7 @@ class Dispatcher:
task_id,
e)
# DEPRECATED: Step 5 handler 架构已替代此方法,保留仅供平滑过渡,确认稳定后删除。
def _mail_auto_complete(self, task_id: str, agent_id: str,
db_path: Path, must_haves: str, outcome=None) -> None:
"""Mail 任务:on_complete 后自动标 done/failed(含幻觉门控)"""
@@ -798,6 +839,7 @@ class Dispatcher:
except Exception as e:
logger.error("Mail %s: auto-complete error: %s", task_id, e)
# DEPRECATED: Step 5 handler 架构已替代此方法,保留仅供平滑过渡,确认稳定后删除。
def _mail_check_reply(self, original_task_id: str, db_path: Path) -> bool:
"""幻觉门控:检查是否有回复邮件(in_reply_to = original_task_id"""
try:
+3 -3
View File
@@ -7,7 +7,6 @@ from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Dict, Optional
from src.daemon.base_task_handler import BaseTaskHandler, VerifyResult
from src.daemon.prompt_composer import PromptComposer, PromptContext
@@ -15,6 +14,7 @@ from src.blackboard.db import get_connection
logger = logging.getLogger("moziplus-v2.handler.mail")
class MailHandler(BaseTaskHandler):
"""Mail 任务 handler。"""
@@ -65,7 +65,7 @@ class MailHandler(BaseTaskHandler):
"""request 验证失败 → 标 failed + 通知发件人"""
self._mark_task_status(db_path, task_id, "failed")
logger.info("Mail %s: request verify failed (%s), marked failed",
task_id, verify.reason)
task_id, verify.reason)
# 通知发件人
try:
@@ -95,7 +95,7 @@ class MailHandler(BaseTaskHandler):
def _check_reply(self, task_id: str, db_path: Path) -> bool:
"""检查是否已回复(查 tasks 表找 in_reply_to 回复邮件)
dispatcher._mail_check_reply 迁移
Mail 回复机制创建新 taskmust_haves JSON 中包含 in_reply_to = original_task_id
不能查 comments 回复邮件是独立的 task不是 comment
+1 -1
View File
@@ -6,7 +6,7 @@ prompt_composer.py — PromptSection Protocol + PromptContext + PromptComposer
import logging
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
from typing import Dict, List, Optional, Protocol, runtime_checkable
logger = logging.getLogger("moziplus-v2.prompt_composer")
+4 -4
View File
@@ -16,11 +16,10 @@ from pathlib import Path
from typing import Any, Dict, List, Optional
from src.blackboard.db import get_connection
from src.daemon.task_type_registry import TaskTypeRegistry
logger = logging.getLogger("moziplus-v2.spawner")
from src.daemon.task_type_registry import TaskTypeRegistry
# ── Prompt 模板 ──
@@ -1125,9 +1124,10 @@ curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_
# 构建续杯 message(Mail 用专用模板,Task 用标准模板)
task_info = self._get_task_info(db_path, task_id) or {}
project_id = task_info.get("project_id", "")
is_mail = project_id == "_mail"
handler = TaskTypeRegistry.get_by_project(project_id)
is_handler = handler is not None
if is_mail:
if is_handler:
must_haves = task_info.get("must_haves", "{}")
try:
meta = json.loads(must_haves) if must_haves else {}
+19 -10
View File
@@ -5,8 +5,9 @@
from __future__ import annotations
import logging
import os
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict, Optional
from src.daemon.base_task_handler import BaseTaskHandler, VerifyResult
from src.daemon.prompt_composer import PromptComposer, PromptContext
@@ -28,7 +29,10 @@ ROLE_SKILL_MAP: Dict[str, str] = {
"claim": "blackboard-claim",
}
SKILL_BASE_PATH = "/Users/chufeng/.sanguo_projects/sanguo_mozi/skills"
SKILL_BASE_PATH = os.environ.get(
"MOZI_SKILL_PATH",
"/Users/chufeng/.sanguo_projects/sanguo_mozi/skills",
)
# ---------------------------------------------------------------------------
@@ -79,7 +83,7 @@ class PriorOutputsSection:
class RoleSkillSection:
"""段 3:角色 Skill 索引+引导语(D8 决策:不注全文)。"""
"""段 3:角色 Skill 全文注入(对齐设计 §2.3 + BootstrapBuilder 行为)。"""
name: str = "role_skill"
priority: int = 30
@@ -91,11 +95,16 @@ class RoleSkillSection:
f"你的角色:{context.role}",
]
if skill_name:
lines.append(f"对应 Skill{skill_name}")
lines.append(
f"请用 read 工具读取 {SKILL_BASE_PATH}/{skill_name}/SKILL.md "
"获取完整操作规范。"
)
skill_path = os.path.join(SKILL_BASE_PATH, skill_name, "SKILL.md")
try:
with open(skill_path, encoding="utf-8") as f:
skill_content = f.read()
if skill_content:
lines.append(skill_content)
else:
lines.append(f"Skill 文件为空:{skill_name}")
except FileNotFoundError:
lines.append(f"Skill 文件不存在:{skill_name}")
else:
lines.append("无对应 Skill 文件,按通用规范执行。")
return "\n".join(lines)
@@ -173,7 +182,7 @@ class TaskConstraintsSection:
class TaskHandler(BaseTaskHandler):
"""黑板标准任务 handler。
- verify: 三信号检查output / comment / terminal status
- 成功 review
- 失败 保持 working ticker 重试
@@ -189,7 +198,7 @@ class TaskHandler(BaseTaskHandler):
def post_complete(self, task_id: str, agent_id: str,
outcome: str, db_path: Path) -> None:
"""Task on_complete:区分 executor 和 review。
executor: 基类统一流程crash verify mark review
review: handle_review_complete verdict done/keep review
"""
+1 -1
View File
@@ -9,7 +9,7 @@ from __future__ import annotations
import logging
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Protocol, runtime_checkable
from typing import TYPE_CHECKING, Dict, Optional, Protocol, runtime_checkable
if TYPE_CHECKING:
from src.daemon.prompt_composer import PromptContext
+15 -15
View File
@@ -1481,21 +1481,21 @@ Parent Task ID: {parent_task.id}
# [Step 5] handler 幻觉门控兜底:check_completion 通过 + working → done
handler = TaskTypeRegistry.get_by_project(self._current_project_id)
if handler and handler.check_completion(task.id, db_path):
conn = get_connection(db_path)
try:
ok = self._transition_status(
conn, task.id, "done",
agent="daemon",
detail={"reason": "mail_auto_done_recheck",
"elapsed_minutes": round(elapsed, 1)},
)
if ok:
reclaimed.append(task.id)
logger.info("Mail %s: ticker recheck found reply, marked done (%.1fm)",
task.id, elapsed)
finally:
conn.close()
continue
conn = get_connection(db_path)
try:
ok = self._transition_status(
conn, task.id, "done",
agent="daemon",
detail={"reason": "mail_auto_done_recheck",
"elapsed_minutes": round(elapsed, 1)},
)
if ok:
reclaimed.append(task.id)
logger.info("Mail %s: ticker recheck found reply, marked done (%.1fm)",
task.id, elapsed)
finally:
conn.close()
continue
conn = get_connection(db_path)
try:
+3 -3
View File
@@ -38,13 +38,13 @@ class ToolchainContextSection:
return render_template(event_type, variables)
# fallback:通用事件描述
lines = [f"## 工具链事件", f""]
lines = ["## 工具链事件", ""]
lines.append(f"- **事件类型**: {event_type or '未知'}")
if event_data:
lines.append(f"- **事件详情**:")
lines.append("- **事件详情**:")
for key, value in event_data.items():
lines.append(f" - {key}: {value}")
lines.append(f"")
lines.append("")
return "\n".join(lines)
def should_include(self, context: PromptContext) -> bool:
+9
View File
@@ -21,6 +21,10 @@ from fastapi.staticfiles import StaticFiles
from src.blackboard.registry import ProjectRegistry
from src.daemon.ticker import Ticker
from src.daemon.task_handler import TaskHandler
from src.daemon.mail_handler import MailHandler
from src.daemon.toolchain_handler import ToolchainHandler
from src.daemon.task_type_registry import TaskTypeRegistry
from src.daemon.spawner import AgentSpawner
from src.daemon.bootstrap import BootstrapBuilder
from src.daemon.dispatcher import Dispatcher
@@ -214,6 +218,11 @@ async def lifespan(app: FastAPI):
watch_interval=inbox_config.get("watch_interval", 1.0),
)
# [Step 5] 注册 TaskType handler(必须在 ticker 启动前)
TaskTypeRegistry.register(TaskHandler())
TaskTypeRegistry.register(MailHandler())
TaskTypeRegistry.register(ToolchainHandler())
ticker = Ticker(
registry=registry,
tick_interval=tick_interval,