feat(round2+round3): 完整实现 A/B/C/D 主线 + round3-F/H 修复

Round2 主线:
- A: 事件落库地基 (RuntimeApp EventStore 单例 + 14 repo wiring)
- B: 执行体对齐 (read-before-edit, verification-before-completion)
- C: 界面对齐 (@opentui/solid, 删除 runtime 依赖)
- D: 经验闭环 (ExperienceMiner, DebuggerRole, CompactorRole)

Round2 补充修复:
- fail-on-missing 反作弊门禁
- projection-store-apply.test.ts 补写
- 3个空壳测试转行为 (evidence-store, recovery-impl, knowledge-store)
- ask 项目根支持 AIRCODING_PROJECT_ROOT
- Worker 事件契约修复 (task.attempt.started → checkpoint)

Round3-F: cpp 工具切换
- 删除 BuiltInToolRegistrar cpp.* 闭包
- 接入 toolchain-cpp 真实 CppToolRegistrar
- canonical envelope {status/output/metadata}
- ExecutorRole system prompt 对齐新工具名

Round3-H: Doctor 5 类报告
- toolchain (cmake/ninja/cppcheck/clangd/g++)
- display (X11/Wayland + ImageMagick)
- network (internet connectivity)
- provider (api_key/base_url/model/connectivity)

Secret 脱敏:
- 状态交接.md: sk- → \${OPENAI_API_KEY}
- .gitignore: 添加 .air/ .claude/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-09 16:13:16 +08:00
parent e383d5f6a7
commit 5e282a39b4
44 changed files with 2905 additions and 1343 deletions

View File

@@ -0,0 +1,269 @@
# 集成测试阶段 opus 审查结果
**审计日期**: 2026-06-08
**项目**: AirCoding V1.0.0 Alpha
**审计模型**: Opus四子代理并行各持不同文档
**审计模式**: 四视角交叉审计(系统架构师 / 开发工程师 / 真实用户 / 测试工程师)
**审计基线**: 第一轮集成修复后commit ddefcbb
---
## 0. 总体结论
第一轮修复**方向正确、happy-path 可用**:四份历史报告的头号 P0工具契约 content/output、shell.run AsyncGenerator、Scheduler 假完成、MainAgent 无上下文、确认门断裂)已真实闭合并有功能级门禁守护。但本轮 opus 交叉审查发现**第一轮未触及的更深层架构问题与新缺陷**
- **架构师**:事件驱动地基在运行路径上是空的(`setRepositories` 从不调用、`task.created` 从不发出、ProjectionStore 运行时从不被事件驱动、TUI 非 OpenTUI、`air ask` 旁路整个调度架构)。判定**不可发布**。
- **工程师**:发现 failed 任务被调度机当成 COMPLETED、Worker exit/result 竞态、fs.edit 参数名不匹配、心跳不刷新。判定**不阻断内测,但对外 Alpha 前必修 P1-1/P1-2/P1-3**。
- **用户**:核心闭环真实跑通无幻觉无假成功,评分 8/10判定**可演示、Alpha 可发布带条件**。
- **QA**5 条新增 gate 中 4 条真实有效,但 P6 门禁引用不存在的测试文件形成假阳性Worker/C++/Projection 仍无真实集成测试。判定**不建议标记 Release READY**。
四视角分歧点在于"发布标准":用户/工程师视 happy-path 可用为 Alpha 达标;架构师/QA 视事实源与门禁完整性未达标。**综合判定:可内测演示(限 `air ask`),但不可对外 Alpha 发布,事件地基与失败处理必须先闭合。**
---
## 1. 系统架构师审查
### 架构结论
**不具备产品演示/Alpha 发布标准。** 第一轮闭合了一批工具契约/Worker 结果/确认门的真实 bug但**事件驱动这一架构地基在运行路径上仍是空的**domain 表从不被写入、`task.created` 从不发出、TUI 不是 OpenTUI、ProjectionStore 运行时从不被事件驱动。`air run` 能跑通"创建文件"是因为它绕过事实源,直接用 WorkerResult 内存对象 + 手工 snapshot 显示结果,掩盖了 INV-1/INV-5/FR-004 运行时整体失效。
存在两条割裂的执行实现:
- `air ask`CLI 进程内自带 LLM→工具循环`ask.ts:82-215`**完全不经过 Scheduler/Worker/IPC**。
- `air run`:走 MainAgent→Scheduler→WorkerManager→子进程→IPC→ExecutorRole 真实链路。
状态交接.md 的 UAT 全部用 `air ask` 验证,因此真实执行链路实际未被 UAT 覆盖。
### P0 问题表(阻断发布)
| # | 问题 | 证据 | 影响 |
|---|---|---|---|
| ARCH-P0-1 | **durable 事件从不投影到 domain 表**`EventStore.setRepositories()` 运行路径从未调用,仅测试调用。运行时 `project()` 内所有 repo 为 null每个 case 静默 no-op | `EventStore.ts:299-312``EventStore.ts:492-935``RuntimeApp.ts:80-84`(只 setTransactionManager | INV-1 运行时整体失效FR-004 不成立domain 表永远为空SQLite 不是事实源 |
| ARCH-P0-2 | **`task.created` 从不发出**`Scheduler.create_tasks()` 注释称发出但函数体只加内存 TaskGraph 节点,无 ingest | `Scheduler.ts:65-78`L76 注释谎称)对比 `Scheduler.ts:149` task.started 确实 ingest | tasks 表无 pending 行task.started 投影 update 不存在的行rebuild_from_db 永远查不到任务 |
| ARCH-P0-3 | **TUI 非 OpenTUI**,是手写 ANSI 转义渲染器;`@opentui/*` 零依赖零引用 | `TuiApp.tsx:27-310``tui/package.json` 无 opentui | 违反 baseline §3/§18 + 需求约束 #3 + FR-016任务 #158 标记 completed 与事实不符 |
| ARCH-P0-4 | **ProjectionStore 运行时从不被事件驱动**`apply()` 运行路径零调用run.ts 手工构造假 snapshot | `run.ts:59-72``run.ts:170-183`EventBus→ProjectionStore 无订阅 | 违反 baseline §18 + INV-5 + FR-016TUI 状态与 DB 可任意不一致 |
| ARCH-P0-5 | **`air ask` 旁路整个调度/Worker 架构**,自带内联执行循环 | `ask.ts:71-78``ask.ts:82-215` | FR-007/FR-008 在主力 demo 命令上不成立;两套执行语义割裂 |
### P1 问题表
| # | 问题 | 证据 |
|---|---|---|
| ARCH-P1-1 | ArchitectureDesigner 发出事件 `session_id:''` 必抛错被 `.catch(()=>{})` 吞掉 | `ArchitectureDesigner.ts:59-73``EventIngestor.ts:204` |
| ARCH-P1-2 | MainAgent 15 态多数无真实触发路径AWAITING/SCHEDULING/SUMMARIZING/ERROR/TERMINATED 无进入点) | `MainAgent.ts:15-30,75-122,278-313` |
| ARCH-P1-3 | Scheduler 13 态部分空壳过场COLLECTING_RESULTS/REVIEWING_WAVE 直接切换200ms 轮询 | `Scheduler.ts:340-356,329-331` |
| ARCH-P1-4 | CapabilityRegistry 接入但运行路径无 discover/load恒空 | `CapabilityRegistry.ts:48``RuntimeApp.ts:71-75` |
| ARCH-P1-5 | 真实 C++ 工具链 CppToolRegistrar 未进主链路live 用 BuiltInToolRegistrar 简化版 | `grep CppToolRegistrar` 无命中;`BuiltInToolRegistrar.ts:288-364` |
| ARCH-P1-6 | contracts ToolRegistry 接口签名与实现背离,靠 `as any` 掩盖 | `contracts/src/tool.ts:100-106` vs `ToolRegistry.ts:91,128,60` |
---
## 2. 开发工程师审查
### 工程结论
第一轮修复方向正确、主路径可用,`tsc --noEmit` 0 错误。但发现第一轮未覆盖的真实缺陷,两项触及"诚实性/正确性"底线failed 任务被伪装成 COMPLETED、Worker exit/result 竞态。happy-path 能跑通UAT 结论可信,但"任务失败"会被系统性伪装成成功。
### P1 问题表(对外发布前必修)
| # | 问题 | 证据 | 修复方向 |
|---|---|---|---|
| ENG-P1-1 | **failed 任务被调度机当成 COMPLETED**PLANNING_WAVE 算 `remaining=pending+running`failed 不计入 → COMPLETEDREPAIRING_OR_CONTINUING 的 `if(failed>0){}` 是空壳retry_planner 从未调用 | `Scheduler.ts:117-122,358-368``grep retry_planner.` 无调用 | PLANNING_WAVE 终态区分 failed → BLOCKED/TERMINATED 或经 RetryPlanner 重试run_until_idle 终态反映失败 |
| ENG-P1-2 | **Worker 退出/结果竞态**WorkerProcess 用 `'exit'``'close'`worker report_result 后立即 process.exit(0)exit 可能先于最后一行 stdout 解析handle_worker_exit 误判 failed | `WorkerProcess.ts:134-142``WorkerManager.ts:339-362``main.ts:92-93` | 监听 `'close'`;或 handle_worker_exit 对无结果做微任务让步后复查worker 端 exit 前 await stdout drain |
| ENG-P1-3 | **fs.edit 参数名不匹配Agent 调用恒失败**:执行器读 `{find,replace}`ExecutorRole/ask.ts 传 `{old_str,new_str}`find 恒 undefined → "Exact text not found" | `tools/fs/index.ts:191-197``ExecutorRole.ts:292``ask.ts:107` | 统一参数名(执行器接受 old_str/new_str 或兼容 find=old_str补 Agent 路径编辑回归 |
| ENG-P1-4 | **Worker 心跳不刷新**worker.heartbeat 无 WorkerManager 处理器record_heartbeat 仅派发时调一次,>5min 任务被判 stalled>10min 被 cancel | `WorkerManager.ts:162-257``Scheduler.ts:177,193``AgentMonitor.ts:90-104` | WorkerManager 注册 worker.heartbeat/checkpoint → record_heartbeat 刷新 |
### P2 问题表
| # | 问题 | 证据 |
|---|---|---|
| ENG-P2-1 | shell.run 流式块顺序错乱且重复(退出后先聚合 stdout/stderr 再 drain 增量 chunks | `tools/shell/index.ts:94-135` |
| ENG-P2-2 | ServiceRegistry 为分叉死代码DB 路径与 RuntimeApp 不一致 | `ServiceRegistry.ts:38-49``RuntimeApp.ts:64-66` |
| ENG-P2-3 | 内建工具成功 envelope 夹带遗留 `call_id/tool_name/type:'text'` 顶层字段,靠 Promise<any> 不报错 | `BuiltInToolRegistrar.ts:180-183` 等 18 处 |
| ENG-P2-4 | ProviderManager.complete_text 硬编码 anthropic provider_id/canonical_format | `ProviderManager.ts:110-119``ask.ts:13,43` |
| ENG-P2-5 | process.kill 工具无权限门perms 全 false可 kill 任意 PID | `BuiltInToolRegistrar.ts:118-120,190-201` |
| ENG-P2-6 | 空 catch 吞错 | `ContextAssembler.ts:127``run.ts:155,157` |
| ENG-P2-7 | ArchitectureDesigner 事件 session_id 为空 | `ArchitectureDesigner.ts:59-73` |
### 第一轮修复正确性核验表
| 第一轮声称 | 核验结论 |
|---|---|
| 内建工具改 canonical {status,output,metadata} | ✅ 部分status/output 已加,但仍夹带 type:'text'/顶层 call_id |
| grep content: 无残留 | ⚠️ 残留多为合法fs.read 输出、layer.content、IPC payload |
| shell.run 两路径正确 | ✅ 消费正确;⚠️ 流式块顺序/重复有缺陷 |
| Scheduler 按 status 终结 | ✅ 已删假完成逻辑;❌ 但 failed 在 PLANNING_WAVE 被当已完成 |
| WorkerProcess on_exit 覆盖退出语义 | ⚠️ 映射对,但 exit/result 顺序竞态未解决 |
| MainAgent 真用 ContextAssembler | ✅ assemble 注入;⚠️ answer 模式 agent_type 误用 'executor' |
| run.ts pendingConfirmation 健壮 | ✅ 空输入/y/n/非y-n 路由成立 |
| ExecutorRole 严格 DONE/失败不 completed/保留原文 | ✅ 全部成立 |
| CapabilityRegistry/ArchitectureDesigner 接入 | ✅ 实例化绑定;但 ArchDesigner 事件 session_id 空P1、Capability 运行时空集 |
| ContextAssembler L3/L6/L8 | ✅ project_files/evidence 签名/tool role 均落地 |
| release.ts findRepoRoot/findBun | ✅ 成立 |
---
## 3. 真实用户 / UAT 审查
### 用户体验评分8/10
核心闭环(创建文件、上下文问答、多文件生成、危险操作确认门、调度执行)全部真实跑通,无幻觉、无假成功。扣分来自 `air run` TUI/readline 交织、新建项目立即 doctor 失败两个体验摩擦点。
### 测试矩阵
| # | 场景 | 结果 | 观察 |
|---|---|---|---|
| 1 | air init | PASS | 创建 .air 结构 + project.json退出码 0 |
| 2 | air ask 创建 hello.txt | PASS | fs.write磁盘内容精确 `HelloWorld`(10B) |
| 3 | air ask 项目有哪些文件 | PASS | answer 模式准确列出真实文件,无幻觉,未列 .air |
| 4 | air ask C++ + CMakeLists | PASS | main.cpp(97B)+CMakeLists.txt(153B)g++ 实测编译运行输出 Hello World |
| 5 | air run 中文删除 + n | PASS | 命中确认门,"Cancelled. No task was created.",文件保留 |
| 6 | air run 中文删除 + y | PASS | 确认后派发 worker 走 shell.run文件被删除 |
| 7 | air doctor | PASS含告警 | 6 项全绿project_structure 报缺 package.json [fixable] |
| 8 | air e2e | PASS | 14/14 gates |
| 9 | /help /tools /results | PASS | 清晰可理解 |
### 最痛问题
1. `air run` TUI 与 readline 双写终端(中)——全屏 TUI 与行式 `> ` 提示符混在同一 stdoutworker 运行时刷屏交错。
2. 新建项目 doctor 立即失败(低-中——init 不生成 package.json紧接 doctor 报 FAIL负面第一印象。
3. glm-5.1 reasoning token 消耗(信息项)——低 max_tokens 时正文可能为空。
### False-positive 风险:低
文件产物均落盘后 cat/ls/g++ 实测复核删除查磁盘确认cpp.build 失败是真实无 cmake优雅降级如实说明。唯一留意/results 是 run 进程内存态,重启不持久。
### 是否可演示/可发布
- **可演示:是**(建议用 air ask输出干净
- **可发布 Alpha带条件**——功能完整、门禁 14/14、确认门中英文生效达 Alpha 线;正式版前收口 run 输入统一、init/doctor 体验、/results 持久化。
---
## 4. QA / 发布门禁审查
### QA 总结
第一轮源码修复方向正确,`release-critical-gates.test.ts` 是本项目第一次出现真正执行被测代码的发布级门禁。但门禁整体三个结构性问题未达"可发布"
1. **P6 门禁形同虚设**——引用的 `projection-store-apply.test.ts` 不存在bun 静默跳过缺失路径,仅靠 workspace-enum.test.ts 让 gate 变绿,**假阳性**。
2. **关键集成路径无真实验证**——28 个测试文件无一真正 spawn worker 子进程、无一真正编译运行 C++。
3. **源码字符串断言占比过高**——28 个测试中 18 个64%)用 readFileSync + toContain只证明"代码还在"不证明"功能正确"。
### 测试矩阵(真实运行)
| 项 | 实测结果 | 备注 |
|---|---|---|
| tsc | EXIT=01.2s | 增量编译(未 clean rebuild|
| air e2e | 14/14 passed4.9s | 见逐 gate 评估 |
| release --dry-run | 3/3 — READY8.6s | |
| P4 Worker IPC | 21 pass/53 expect | 无真实 spawn |
| P5 C++ | 5 pass/10 expect | 仅 1 文件纯源码断言,无真实编译 |
| P8 全回归 | 142 pass/346 expect/21 files | 体量真实但大量字符串断言 |
### 新增 gate 有效性评估
`release-critical-gates.test.ts`5 条)——质量最高:
| Gate | 判定 |
|---|---|
| 1 tool 用 output 非 content | ✅ 真实调用工具断言 output 存在/content undefined |
| 2 shell.run finalcall+streaming | ✅ 真跑 printf ok 断言 exit_code/stdout/is_final |
| 3 Scheduler 不假完成 | ✅ 命中核心 bug workerManager 是字面量 mock |
| 4 MainAgent answer 用 context | ✅ 真实 ContextAssembler + 临时文件 |
| 5 危险操作 CONFIRMING→IDLE | ✅ 真实 MainAgent 状态机 |
5 条中 4 条真实执行被测逻辑——**合格,是门禁里唯一可信功能层**。
`run-command-regression.test.ts`3 条)——全部源码字符串断言,不执行 run 命令,仅防回退快照。
### 仍缺失的关键 gate
| # | 缺失项 | 风险 |
|---|---|---|
| G1 | Worker 真实 spawn + IPC round-tripworker-fixture 自承认 stub | 最高 |
| G2 | 真实 C++ build/run | 高 |
| G3 | Worker exit consistency 端到端 | 高 |
| G4 | Projection rebuild/replay门禁引用文件不存在假绿 | 高 |
| G5 | False-positive 成功检测ExecutorRole 无任何测试) | 高 |
| G6 | complex C++ build/run e2e | 中-高 |
### QA P0/P1/P2
**P0阻断发布**
- QA-P0-1 修复 P6 假阳性门禁(补 projection-store-apply.test.ts 或移除路径并 fail-on-missing
- QA-P0-2 e2e runTest 加 fail-on-missing任一路径不存在直接 fail
- QA-P0-3 Worker 真实 spawn round-trip 接入 P4
**P1**
- QA-P1-1 真实 C++ build/run e2e
- QA-P1-2 ExecutorRole DONE/失败不 completed 功能测试
- QA-P1-3 Worker exit→result 一致性端到端
- QA-P1-4 Projection rebuild/replay 覆盖
**P2**
- QA-P2-1 降低源码字符串断言占比64%
- QA-P2-2 tsc clean rebuild 验证
- QA-P2-3 P7 direct-mode mock 标注边界
### 发布门禁建议
**当前不建议标记 Release READY**,尽管 release --dry-run 3/3。理由release 的绿建立在 e2e 14/14 之上,而 14/14 里 P6 假阳性、P4/P5 源码断言冒充集成。最低放行QA-P0-1/2/3 完成 + 手工 UAT 脚本化为可重放 e2e 纳入门禁。
---
## 5. 四视角交叉综合
### P0 汇总(阻断对外发布)
| # | 问题 | 来源视角 | 根因 |
|---|---|---|---|
| 1 | EventStore.setRepositories 运行路径从不调用 → domain 表恒空 | 架构师 | INV-1/FR-004 地基失效 |
| 2 | task.created 从不发出 | 架构师 | 事件溯源断链 |
| 3 | failed 任务被调度机当成 COMPLETED | 工程师 | 失败伪装成功(触碰红线)|
| 4 | Worker exit/result 竞态误判 failed | 工程师 | 成功也可能被误判 |
| 5 | ProjectionStore 运行时不被事件驱动run.ts 手工 snapshot | 架构师 | INV-5/FR-016 |
| 6 | P6 门禁引用不存在文件,假阳性 | QA | 门禁完整性 |
| 7 | air ask 旁路调度/Worker 架构 | 架构师 | FR-007/008 双实现割裂 |
| 8 | TUI 非 OpenTUI | 架构师 | FR-016/约束#3 |
### P1 汇总
fs.edit 参数不匹配恒失败工程师、Worker 心跳不刷新工程师、ArchitectureDesigner 事件 session_id 空被吞(架构师+工程师、MainAgent/Scheduler 状态机空壳架构师、CapabilityRegistry 运行时空集(架构师)、真实 C++ 工具链未进主链路架构师、Worker/C++/Projection 无真实集成 gateQA
### 与前几轮对比
| 维度 | 前几轮 | 第一轮修复后(本轮实测) |
|---|---|---|
| 工具契约 output/content | 头号 P0 | ✅ 已修 + 真实 gate |
| shell.run AsyncGenerator | 阻断 | ✅ 已修 + 真实验证 |
| Scheduler 假完成 | 阻断 | ✅ 已删假逻辑;❌ 但 failed→COMPLETED 新问题 |
| MainAgent 上下文/确认门 | 阻断 | ✅ 已修 + 真实验证 |
| ProjectionStore-only/TUI snapshot | Deepseek/Gpt5.5 P1 | ❌ 未修(更深:本轮查实 setRepositories 从不调用)|
| durable task events 主路径 | Gpt5.5 P0 | ❌ 未修根因task.created 不发出)|
| Worker 真实 spawn | 一直缺失 | ❌ 仍缺失(自承认 stub|
| C++ 真实 build/run | 一直缺失 | ❌ 仍缺失 |
**本轮新发现**setRepositories 从不调用最严重、task.created 不发出、TUI 非 OpenTUI、failed→COMPLETED、Worker exit/result 竞态、fs.edit 参数不匹配、P6 假阳性门禁、ArchitectureDesigner 事件被吞。
---
## 6. 发布建议与修复优先级
**综合判定:可内测演示(限 air ask不可对外 Alpha 发布。**
14/14 e2e 与 3/3 release 全绿,但这些门禁不触碰本轮 P0 任何一条——绿灯与可用性正交,这正是 MiniMax 已警告、本轮仍重演的盲点。
按修复优先级(遵守"不接受架构降级"原则,全部为补齐而非删功能):
1. **闭合事件地基**RuntimeApp.start 调用 `eventStore.setRepositories({...})` 注入全部 domain repocreate_tasks 真正 ingest task.created。P0-1/P0-2
2. **修复失败处理**Scheduler PLANNING_WAVE 终态区分 failed接线 RetryPlannerrun_until_idle 反映失败。P0-3
3. **修复 Worker 竞态**WorkerProcess 监听 'close' 或退出前复查 result。P0-4
4. **统一执行路径**air ask 复用 Scheduler→Worker删除 CLI 内联循环。P0-7
5. **收口 Projection 事实源**EventBus→ProjectionStore.apply→TUI删 run.ts 手工 snapshot。P0-5
6. **门禁完整性**e2e runTest fail-on-missing补 P6 真实测试;补 Worker spawn / C++ build / false-positive / projection rebuild gate。P0-6 + QA-P0
7. **TUI 技术栈归位**:接 @opentui/*或走正式架构变更声明不可默默降级P0-8
8. P1 批量fs.edit 参数、心跳刷新、ArchDesigner session_id、状态机补齐、CapabilityRegistry 加载、真实 C++ 工具链进主链路。
**核心教训重申**代码质量指标tsc/门禁数)与产品可用性指标正交。第一轮把"被测代码从不执行"推进到"核心修复点被真实执行"是实质进步但门禁完整性fail-on-missing与集成层真 spawn / 真编译 / 真事件落库)仍是发布前硬缺口。
---
*审计模型: Opus4 子代理并行)*
*审计时间: 2026-06-08*
*仓库状态: 源码未改动(只读审计)*