Files
DevFlow/docs/待审查.md

968 lines
182 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 待审查队列
> **职责分离**:
> - **主会话**(编码合并后 / 巡检)→ 【**写入**】"当前队列"区,登记待审查项(范围 + 维度 + commit)。
> - **审查 agent**(独立会话 / workflow)→ 【**读取**】"当前队列",执行审查,完成回填结论 + 移到"已审归档"。
>
> 这里**只放审查登记**,不放推进待办(推进待办见 [todo.md](./todo.md))。
> 主会话**不在这里做审查**,只登记 + 读 todo 推进。审查发现的【待修代码项】回流 todo 作推进待办。
## 审查规则(审查 agent 遵守)
- 只读审查,**不改代码**。
- **独立 grep/read 核验源码,不信文档 / 会话描述 / 设计声明的结论**(防上下文污染)。
- 每条 finding 须 `file:line` 源码佐证 + evidence。
- 严重度:high(破坏性 / 数据错 / 回归) / med(逻辑瑕疵 / 边界漏) / low(风格 / 可读)。
- 完成回填:总体 verdict(PASS / ISSUES / WATCH)+ high/med/low 清单 + 待修项回流 todo。
## 状态图例
🟡 待审 / 🔵 审查中 / ✅ 已审(结论) / 🔴 待修(有 high 问题)
---
## 当前队列
### CR-260616-58 F-260616-07 阶段3 AiNode 自审闸门(verdict=fail 作 DAG 节点闸门阻断下游)(e16d038) — ✅ 已审(PASS)
- **复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: `cargo check --workspace` **EXIT 0**(6 pre-existing dead_code warnings 无关本 CR)/ `cargo test -p df-nodes --lib` **82 passed; 0 failed; 1 ignored** (+3 gate 单测:1076-1103)。
- **①gate_should_block 决策矩阵完备 PASS**:ai_node.rs:426-428 `gate && verdict == "fail"` 简洁正确,gate=false 全放行(阶段2 兼容),gate=true+仅 fail 阻断,unknown/非标 verdict 保守不阻断(单测:1096-1103 覆盖)。
- **②execute 末尾 Err 路径正确 PASS**:ai_node.rs:574-582 update_field 落库在前(审查痕迹不丢) + :615-629 闸门检查后置,verdict=fail 返回 Err 正确。
- **③复用 executor first_err + ②-4 回调语义 PASS**:df-workflow/src/executor.rs:128-178 first_err 捕获首个 Err 后立即中止后续层(下游 human_review 不跑) + workflow.rs:292-310 failed 触发 regression_target 退回 in_review(review_rounds+1,task_advance_node.rs:515-547 验证)。
- **④gate 默认 false 向后兼容 PASS**:ai_node.rs:619 `.unwrap_or(false)` 阶段2 行为不变,AiNode schema(:331-346)无 gate 字段,仅 AiSelfReviewNode 有(:644)。
- **⑤testing 模板 gate:true 启用 PASS**:task_workflow_templates.rs:65-69 `"gate": true` 正确注入 + 单测:139-144 断言验证。
- **⑥schema 契约正确 PASS**:ai_node.rs:644 gate 布尔字段,非 required,description 完整。
- **⑦文件锁独立性 PASS**:ai_node.rs + task_workflow_templates.rs 独占,df-workflow 零改动(git show e16d038 确认)。
- **⑧选型论证 PASS**:ai_node.rs:605-613 注释完整(方案 A 最简 vs B 暂缓条件引擎 vs C executor 改核心),选 A 语义贴近「自审结果作为闸门」且可开关。
- **⚪ CR-58-1(low,注释冗余)**:ai_node.rs:605-613 选型注释 9 行,但提供设计决策历史,对后续维护有价值,可保留。
- **待修项回流 todo**: **无**(8 维度全 PASS,1 low 注释冗余非阻断)。
### CR-260616-59 波16 F-04c前端Settings池编辑(enabled toggle+weight输入+IPC绑定)(3f90aad) — 🟡 待审
- **范围**: workflow w94jdgv9n agent A。types.ts AiProviderConfig 补 enabled?/weight?(snake_case 对齐 serde)+ api/ai.ts updateProviderPool(providerId,enabled,weight) wrapper L146 + Settings.vue 卡片 detail 区 enabled toggle(L60)+ weight 数字输入(L69)+ handler onPoolToggle(乐观更新+失败 revert)/onPoolWeightChange(debounce 300ms+clamp[0,100]+NaN 兜底)+ onUnmounted 清 _poolTimer + CSS + i18n zh/en 7 key。交互决策:卡片即时调 IPC(非 saveProvider)——池调度参数即时生效(reload_provider_caps 变更即重建)+ 避免 saveProvider 密钥迁移分支 + 对齐 syncConcurrency 即时调模式。
- **维度**: ①updateProviderPool wrapper 对齐现有 invoke 风格 ②AiProviderConfig enabled?/weight? 可选(snake_case serde 对齐) ③即时调 IPC 决策合理 ④debounce 300ms+clamp+NaN 兜底 ⑤乐观更新+失败 revert ⑥i18n 双语。
- **commit**: 3f90aad。
- **主代核查**: vue-tsc EXIT 0 + grep updateProviderPool/AiProviderConfig/toggle。
- **审查 agent 待复审重点**: ①updateProviderPool invoke 参数命名(camelCase vs snake_case Tauri 2 转换) ②乐观更新 revert 逻辑 ③debounce timer 清理(onUnmounted) ④i18n key 完整。
- **待修项回流 todo**: **无**(前端就绪,后端 CR-56 已就绪)。
### CR-260616-60 波17 B-03b-R10前端审批清理(终态清pending+互斥锁防重复IPC)(0289d8c) — 🟡 待审
- **范围**: workflow w9xypd8ug agent B。src/stores/project/workflow.ts ⑤终态不清(startEventListener 加 else-if,workflow_completed/failed/node_cancelled 且 execution_id 匹配清 pendingApproval,按 exec_id 限定防并发误清)+ ⑦互斥(模块级 _approvalInFlight 单例标志,approve/cancel 入口检查+try/finally 释放,防双击重复 IPC)。④单槽→Map 推迟(state 形态变更牵动多消费者,后端串行闸门保证单执行不并发,现实触发需并发工作流)。波17 B-03b 后端 workflow.rs 0 实施(⑧⑩⑪ 报告失效/已修复/已有兜底,③ 真实潜伏跨 crate df-core 缺 execution_id 留观察)。
- **维度**: ⑤终态清 execution_id 限定(防并发误清)+ ⑦互斥 _approvalInFlight 单例(跨调用方统一)+ ④推迟理由(state 形态变更+后端串行)。
- **commit**: 0289d8c。
- **主代核查**: vue-tsc EXIT 0。
- **审查 agent 待复审重点**: ⑤终态清 execution_id 匹配逻辑(防误清)+ ⑦互斥 _approvalInFlight try/finally 释放(异常路径不泄漏)。
- **待修项回流 todo**: **无**(⑤⑦ 实施,④ 推迟有理由)。
### CR-260616-61 波18 ARC df-core改名df-types(机械全workspace 54处)(4be1591) — 🟡 待审
- **范围**: 单 agent workflow wkptjpojo。git mv crates/df-core → df-types + 9 Cargo.toml(name+依赖+path)+ perl 替换 .rs df_core→df_types 54 处 + lib.rs doc + 8 注释连字符 df-core→df-types + Cargo.lock 自动迁移。
- **维度**: ①机械改名无逻辑(波15 走查核验 0 宏自引用/0 字符串字面量自引用) ②grep df_core 代码侧归零 ③cargo build --workspace EXIT 0 + 分 crate test 全绿(≥基线:df-nodes 82/df-workflow 23/df-ai 107/df-ideas 20/df-storage 40)④Cargo.lock 自动迁移 ⑤glob members crates/* 自动适应。
- **commit**: 4be1591。
- **主代核查**: cargo check --workspace EXIT 0 + grep df_core 代码侧 0 残留 + df_types 新名确认。
- **审查 agent 待复审重点**: ①grep df[_-]core 代码侧(.rs+Cargo.toml)归零 ②分 crate test 无回归 ③无字符串/宏自引用遗漏。
- **待修项回流 todo**: **无**(机械改名,文档同步 .md/project.ts 注释 deferred 非阻塞)。
### CR-260616-36 batch60 F-09A 隔离补清 + F-13 性能优化 + AE-03 write_file diff 预览(路径B) — ✅ 已审(PASS)
- **复审结论(2026-06-16·CR-36 agent 亲跑 cargo+vue-tsc+test,723s)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: `cargo check --workspace` EXIT 0(4 warning 全 pre-existing:mod.rs:282 PendingApproval.diff never read 误报 / state.rs:146 releases+node_executions dead_code)/ `cargo test -p df-ai` **37 passed** / `npx vue-tsc --noEmit` EXIT 0。
- **F-13 messages.clone() 等价性 PASS**:stream_recv.rs:137-144 stream_llm 签名不接 session_arc + 重试块(agentic.rs:259-363)到 process_tool_calls(:476)间 0 个 session.messages.push(push 在 :391/395/446/450 之后)+ CR-260616-35 回归核验(保文/退避/permit RAII/guard.reset/request 重建全无回归)。
- **F-13 sys_tokens 缓存 PASS**:agentic.rs:172-174 system_prompt 是 run_agentic_loop owned String 参数,整 loop 只读(:174 estimate_text + :227 ChatMessage::system),无 mutate。
- **AE-03 build_write_file_diff 安全 PASS**:audit.rs:508-520 预读旧文件路径未校验,但桌面应用威胁模型(LLM 生路径非远程攻击者)+ read 仅展示不外泄 + handler validate_path 执行时兜底,可接受风险。
- **AE-03 diff 双路径 PASS**:实时(audit.rs:588-609 PendingApproval.diff clone + AiApprovalRequired.diff move 双注入)+ 恢复(audit.rs:282 diff=None 故意,DB ai_tool_executions 无 diff 列,前端 ToolCard v-if tc.diff 兜底)。types 对齐(mod.rs:101/282 Option<String> + types.ts:198/244 diff? + useAiEvents.ts:239 传播)。
- **F-09A 单例补清 PASS**:commands.rs:860 stop_flag.store(false,SeqCst) 在生成中分支置 true(:840)后复位覆盖两路径 + :861 agent_language=None + 前端 useAiConversations.ts:42-45 补清 queue/generatingConvId/agentRound/searchQuery + 未动 AiSession 单例(B 路线边界)。
- **⚪ low**:mod.rs:282 PendingApproval.diff "never read" warning — 误报,值经 clone 进 AiApprovalRequired event payload(audit.rs:609)前端消费,dead-code 分析不见直接 read。
- **待修项回流 todo**: **无**(5 重点全 PASS,1 low 误报)。
- **范围**: 3 agent 合并批(workflow whae812z5,主代独立核查全过 + cargo check --workspace EXIT 0 + vue-tsc EXIT 0)。①**F-09 A 路线**(单例软隔离补漏清字段,不动 B 多会话架构):前端 `useAiConversations.ts:42-45` newConversation 补清 `queue=[]/generatingConvId=null/agentRound=0/searchQuery=''`(防旧会话排队消息带进新会话 + 后台事件错路由 + 侧栏旧搜索过滤);后端 `commands.rs:842-843` ai_conversation_create 补清 `stop_flag.store(false,SeqCst)`(关键:上方生成中分支置 true 停旧 loop 不复位则新会话 loop 启动即见 stop 退出) + `agent_language=None`(防沿用旧语言设置)。②**F-13 性能**:`agentic.rs:173` system_prompt token loop 外算一次缓存为 sys_tokens(整个 loop 不变参数,原每轮+每次重试重算);`:219-225` 外层 messages 构建用 sys_tokens;`:262-270` 重试循环改 `messages: messages.clone()` 复用外层 Vec(删每次重试的 session_arc.lock + build_for_request 全量 clone + estimate_text)。安全前提核验:stream_llm 不接 session_arc + 重试块到 process_tool_calls 间无 push,重试内 session.messages 与外层一致,重建等价复用。③**AE-03 路径 B**(审批 payload 加 diff,非路径 A):`tool_registry.rs:32` generate_diff fn→pub(crate)(复用 F-260615-10 LCS);`audit.rs:508-520` 新增 build_write_file_diff(从 args 取 path+content → 预读旧文件 tokio::fs::read_to_string → 无变化/旧文件缺失/读失败均 None → generate_diff);`audit.rs:588-609` 仅 write_file draft 挂起审批前预计算 approval_diff 注入 PendingApproval.diff(:600 clone)+AiApprovalRequired.diff(:609 move);恢复路径 diff=None(文件可能已变);`mod.rs:101/269` AiApprovalRequired+PendingApproval 加 `diff: Option<String>`;`types.ts:198` AiApprovalRequired 加 `diff?: string`;`useAiEvents.ts` event.diff 传 toolCall 信息;`ToolCard.vue:40-41` 模板 `v-if="tc.diff"` diff 块 + :394 diffLines computed(按 +/-/space 前缀拆分)+ :677-705 样式(add 绿 del 红 ctx 灰 + token 复用)。
- **维度**: ①F-09 单例字段补清完整性(stop_flag 复位是否覆盖所有生成中→新建路径/agent_language 影响面) + B 路线边界(确认未误动 AiSession 单例) ②F-13 行为不变性核验(sys_tokens 缓存是否真不变/messages.clone() 复用是否等价重建/stream_llm 不持 session_arc 前提是否成立) + 锁持有期缩短收益确认 ③AE-03 路径 B 安全性(预读旧文件只读不改/审批拒绝不执行 handler/路径不校验风险——恶意路径读失败仅 None) + diff 注入完整性(实时挂起+恢复路径双覆盖/前端消费链 event→toolCall→模板) + generate_diff pub(crate) 暴露面。
- **commit**: d00b30f(重构: AI聊天可靠性批次)。
- **主代独立核查**: ✅ 全过(useAiConversations:42-45 补清 / commands:842-843 stop_flag.store(false)+agent_language=None / agentic:173 sys_tokens loop 外缓存 + :219-225 外层用 + :262-270 重试 messages.clone() 复用 + 保文退避逻辑未动(Partial 不重试/retry_deadline 30s) / tool_registry:32 generate_diff pub(crate) / audit:508-520 build_write_file_diff 预读+无变化/缺失 None+复用 generate_diff / audit:588-609 仅 write_file 生成 diff 注入 PendingApproval(clone)+AiApprovalRequired(move) / mod:101+269 diff 字段 / types:198 diff? / ToolCard:40-41 模板 + :394 diffLines + :677-705 样式 / cargo+vue-tsc 双 EXIT 0)。
- **审查 agent 待复审重点**: ①F-13 重试 messages.clone() 复用等价性——核验 stream_llm 签名(stream_recv.rs:137 不接 session_arc) + 重试块到 process_tool_calls 间确实无 session.messages.push(若遗漏则重试复用旧 messages 致 tool_calls 丢失) ②F-13 sys_tokens 缓存——核验 system_prompt 确为 run_agentic_loop 不变参数(整个 loop 期间无 mutate) ③AE-03 build_write_file_diff 安全——预读旧文件 `tokio::fs::read_to_string(path)` 路径未校验(恶意 path 读失败仅 None 不产生危害,但核验无 symlink 逃逸读敏感文件风险——审批只读,write_file handler 自身 validate_path 执行时兜底) ④AE-03 diff 注入双路径——实时挂起(audit:588-609 有 diff)+恢复路径(PendingApproval diff 字段是否持久化,若仅内存则重启恢复审批无 diff) ⑤文件锁独立性——3 agent 改动文件无重叠(F-09: useAiConversations/commands / F-13: agentic / AE-03: mod/tool_registry/audit/types/useAiEvents/ToolCard),useAiEvents.ts Agent C 独占(A/B 未碰)确认无冲突。
### CR-260616-37 F-11 审批续跑 iteration 累计计数(跨审批不重置) — ✅ 已审(PASS)
- **复审结论(2026-06-16·CR-37 agent 亲跑 cargo+test,658s)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code:diff field / releases+node_executions 未读)/ `cargo test -p df-ai` **37 passed**(retry 6 + context 18 + openai_compat 8 + stream_recv 5,finished 2.59s)。
- **iteration_used 生命周期完备 PASS**:grep 全消费点 **9 处**——ai_chat_send(:55 reset 0)/ ai_regenerate(:152 reset 0)/ ai_chat_edit_last(:412 reset 0)/ ai_continue_loop(:601 reset 0 + 传 0)/ ai_approve 拒绝(:260 累计读)/ ai_approve 通过(:313 累计读)/ agentic loop 递增(:210 iteration+1 写回)/ mod.rs:215 字段定义 / mod.rs:230 new() init 0。ai_conversation_create 隐式从 0(新会话 new init 0)。**无遗漏**。
- **start_iteration 透传链 PASS**:ai_approve(:260/:313 读 session.iteration_used)→ try_continue_agent_loop(:578 接 start_iteration 参)→ spawn(:675 透传)→ run_agentic_loop(:114 接参)→ for iteration in start_iteration..max_iterations(:176 使用)。每环 file:line 佐证无断链。
- **两路径区分 PASS**:ai_approve 累计(F-260616-11,不 reset 读 iteration_used)vs ai_continue_loop 重计(F-260616-03 决策a,reset+传 0)vs 新消息(reset 0)严格区分。注释 agentic.rs:499-501 + commands.rs:258-259/311-312/599-600 均写明归属。
- **边界 start≥max PASS**:for 循环空区间不执行 + converged 保持 false(:170)→ 命中 :502 `if !converged` → emit AiMaxRoundsReached(:518-520) + return,防无限审批续跑烧 token,符合 F-11 语义。
- **并发安全 PASS**:session.iteration_used=iteration+1 在 agentic.rs:198-211 同一 session_arc.lock().await 块(与 :199 active_conversation_id 一致性校验共享锁),锁模式与 :424-452/:474-477 一致。
- **mod.rs 字段 PASS**::215 `pub iteration_used: usize`(无符号防负)+ :230 new init 0 + agentic.rs:176 循环边界 + :210 一致性块更新。
- **待修项回流 todo**: **无**(6 重点全 PASS,零问题)。
- **范围**: F-260616-11 决策a 落地(与 batch60 三项合批 commit d00b30f)。`mod.rs:215/230` AiSession 加 `iteration_used:usize` 字段+new() init 0;`agentic.rs:114` run_agentic_loop 加 `start_iteration:usize` 参数 +`:176` loop 边界改 `start_iteration..max_iterations` +`:210` 一致性校验块更新 `session.iteration_used=iteration+1` +`:578` try_continue_agent_loop 加 start_iteration 参数 +`:675` spawn 透传 +`:499-501` 注释区分两路径;`commands.rs:55/152/412` ai_chat_send/ai_regenerate/ai_edit_last reset iteration_used=0(新生命周期)+`:260-262` ai_approve 拒绝续跑读 session.iteration_used 累计传 +`:313-315` ai_approve 通过续跑累计传 +`:601` ai_continue_loop reset iteration_used=0 传 0(F-03 决策a 达max重计区分)。两路径严格区分:审批续跑(ai_approve 累计透传 iteration_used) vs 达max续跑(ai_continue_loop reset+传0,F-03 决策a 用户授权重来) vs 新消息(reset 0)。
- **维度**: ①iteration_used 字段生命周期完备性(所有会话生命周期入口 reset 覆盖——create/send/regenerate/edit_last/continue_loop 是否齐全无遗漏路径) ②start_iteration 透传链完整(agentic run_agentic_loop←try_continue_agent_loop←ai_approve 两个调用点,无断链) ③两路径区分正确性(ai_approve 累计 vs ai_continue_loop 重计,F-11/F-03 决策 a 分别归属无混淆) ④边界 case(start≥max loop 空区间→converged=false→AiMaxRoundsReached,防无限审批烧 token,符合 F-11 语义) ⑤并发安全(iteration_used 在一致性 lock 块更新,与现有 lock 模式一致)。
- **commit**: d00b30f(同 CR-36 合批)。
- **主代独立核查**: ✅ 全过(mod.rs:215/230 iteration_used 字段+init / agentic.rs:114 start_iteration 参数+:176 loop 边界+:210 一致性块 iteration+1+:578 try_continue 加参+:675 spawn 透传+:499-501 注释两路径 / commands.rs:55/152/412 reset 0+ai_approve:260/313 累计读 iteration_used+ai_continue_loop:601 reset 传0 / cargo check --workspace EXIT 0)。
- **审查 agent 待复审重点**: ①iteration_used 生命周期完备性(所有会话生命周期入口 reset 覆盖无遗漏——create/send/regenerate/edit_last/continue_loop/ai_approve 全路径核验) ②start_iteration 透传链(run_agentic_loop←try_continue_agent_loop←ai_approve 两调用点无断链) ③两路径区分(ai_approve 累计 vs ai_continue_loop 重计,F-11/F-03 决策a 归属无混) ④边界 start≥max loop 空区间→converged false→AiMaxRoundsReached(防无限审批烧 token) ⑤并发安全(一致性 lock 块更新)。
### CR-260616-38 F-07 df-ai-core trait 下沉拆 crate(解锁 F-03 注入) — ✅ 已审(PASS)
- **复审结论(2026-06-16·CR-38 agent 亲跑 cargo+test,137s)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ideas adversarial` **11 passed**(a1-a11,含 7 heuristic + 4 LLM mock)。
- **provider.rs 迁移完整 PASS**:df-ai-core/src/ 仅 lib.rs+provider.rs(Glob 确认),provider.rs:1-235 含 LlmProvider trait(:208-235)+ 全数据结构(CompletionRequest/ChatMessage/MessageRole/ToolDefinition/ToolCall/CompletionResponse/TokenUsage/StreamChunk/ToolCallDelta/StreamResult),注释 :6-8 声明「零 IO」;grep ContextManager/TokenEstimator/AiToolRegistry/build_provider/HTTP impl 在 df-ai-core 内**零命中**(业务逻辑+HTTP impl 确留 df-ai);df-ai Cargo.toml:8 加 df-ai-core 依赖。
- **re-export 透明 PASS**:df-ai/src/lib.rs:17 `pub use df_ai_core` + provider.rs:15 `pub use df_ai_core::provider::*`;grep `use df_ai::provider` **11 处**实际消费(idea.rs/project.rs/ai_node.rs/agentic.rs/stream_recv.rs/title.rs/knowledge_inject.rs/commands.rs/audit.rs 等)workspace 编译期全过;workspace Cargo.toml members=`["crates/*"]` glob 自动覆盖 df-ai-core 无需手动加。
- **构造注入语义 PASS**:adversarial.rs:19 直接 use df_ai_core(不引 df-ai 重依赖)+ :96-98 `provider: Option<Arc<dyn LlmProvider>>` + :101 new(Arc)→Some + :107 heuristic()→None + :112-133 evaluate() 调度;调用方 idea.rs:192 new(Arc::from(p))=Some / :193 heuristic()=None 等价无遗漏。
- **EvaluatedBy 三态 PASS**:adversarial.rs:28-37 Llm/Heuristic(#[default])/HeuristicFallback + :49-50 `#[serde(default)]` 向后兼容老数据;:112-133 调度 Some+Ok=Llm / Some+Err=warn+HeuristicFallback / None=Heuristic 三态完备。
- **build_default_provider PASS**:idea.rs:265-279 list_all().ok()? DB 查失败 None + is_default 优先 find + 首条 fallback + build_provider_for Err→warn+None + :191-194 None 直走 heuristic 语义正确。
- **Box→Arc 转换 PASS**:idea.rs:192 `Arc::from(Box<dyn LlmProvider>)` 标准库转换(堆分配复用),cargo check EXIT 0 编译期证类型匹配。
- **待修项回流 todo**: **无**(6 重点全 PASS,零问题)。
- **范围**: F-260614-07 4 决策落地(workflow wwtn2knn6)。①df-ai-core 新 crate:`crates/df-ai-core/`(Cargo.toml serde+async-trait+futures + src/lib.rs `pub mod provider` + src/provider.rs 迁移 trait+数据结构 LlmProvider/ChatMessage 等,不含 HTTP impl);②df-ai re-export:`crates/df-ai/Cargo.toml` 加 df-ai-core 依赖 + `src/provider.rs:15` `pub use df_ai_core::provider::*` + `src/lib.rs:17` `pub use df_ai_core`(外部 use df_ai::provider 路径不变,df-nodes 零改动,workspace Cargo.toml 零改动 members=crates/*);③df-ideas 构造注入:`crates/df-ideas/Cargo.toml` 加 df-ai-core+async-trait + `src/adversarial.rs:18` use df_ai_core::provider::LlmProvider + struct 加 `provider:Option<Arc<dyn LlmProvider>>`(:96) + `new(provider:Arc<dyn LlmProvider>)`(:101 调用方 Some/None 分支语义等价 Option)+ `heuristic()`(:107) + `evaluate(&self)`(:111) + `evaluate_with_llm`(:138) + EvaluatedBy 三态 enum(:28) + AdversarialEval.evaluated_by 字段(:49) + LLM 失败→warn+HeuristicFallback 降级(:122);④src-tauri 装配:`idea.rs:190` build_default_provider(DB is_default→build_provider→Option<Box<dyn LlmProvider>>:265) + :192 Some→`AdversarialEngine::new(Arc::from(p))` / None→`heuristic()`(:193)。
- **维度**: ①re-export 透明性(df_ai::provider::LlmProvider 路径 df-nodes/df-project 等外部 use 无断裂) ②trait 下沉边界(LlmProvider trait+纯数据结构下沉,ContextManager/TokenEstimator/AiToolRegistry 业务逻辑确留 df-ai 未误迁) ③构造注入语义(new(Arc) vs 决策② Option<Arc>——调用方分支等价性,无遗漏 Some/None) ④EvaluatedBy 三态正确(Llm/Heuristic/HeuristicFallback,provider None 全走 Heuristic) ⑤Box→Arc 转换(idea.rs:192 Arc::from(Box<dyn LlmProvider>) 编译通过) ⑥build_default_provider DB 读(is_default 查询+无默认兜底 heuristic) ⑦adversarial 7 单测改 heuristic() 构造后全过。
- **commit**: 2069f79(重构: df-ai-core trait下沉+导入历史)。
- **主代独立核查**: ✅ 全过(df-ai-core 新建 Cargo.toml+lib.rs+provider.rs / df-ai provider.rs:15+lib.rs:17 re-export+Cargo.toml 加依赖 / df-ideas adversarial.rs:18 use df_ai_core:96 provider 字段+:101 new(Arc)+:107 heuristic+:111 evaluate(&self)+:138 evaluate_with_llm+:28 EvaluatedBy+:49 evaluated_by+:122 HeuristicFallback 降级 / idea.rs:190 build_default_provider+:192 new(Arc::from)+:193 heuristic+:265 fn / cargo check --workspace EXIT 0(5 warning 全 pre-existing dead_code))。
- **审查 agent 待复审重点**: ①provider.rs 迁移完整性(df-ai 原 trait+数据结构全部下沉,df-ai/src/provider.rs 仅剩 re-export,retry/openai_compat/anthropic_compat HTTP impl 确留 df-ai 未断) ②df-nodes/df-project 外部 use df_ai::provider 编译验证(re-export 透明无断裂,实际 grep df_ai::provider 消费点) ③EvaluatedBy 三态语义(provider Some+LLM 成功=Llm / Some+LLM 失败=HeuristicFallback / None=Heuristic,evaluate_internal 调度正确) ④build_default_provider DB is_default 查询正确+build_provider 复用现有工厂 ⑤Box→Arc::from 编译期验证(cargo EXIT 0 已证)。
### CR-260616-39 F-06 导入历史项目 scan 第二步(LLM description+monorepo+批量并发) — ✅ 已审(PASS)
- **复审结论(2026-06-16·CR-39 agent 亲跑 cargo+test+vue-tsc,215s)**: ✅ **PASS** — 🔴0 🟡0 ⚪2(agent 标题 🟡1 系笔误,待修项表全 low 无 med)
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-project` **15 passed**(detects_monorepo_pnpm/npm_workspaces/non_monorepo/rust/full_stack + strips_frontmatter_and_toc_and_badges + image_collection_skips_badges + extract_desc 三测 + discover_monorepo/flat_children)/ `npx vue-tsc --noEmit` EXIT 0。
- **is_monorepo 判定 PASS**:scan.rs:100-133 4 marker(pnpm-workspace/lerna/turbo/nx)+ package.json workspaces 兜底(:122-131);仅判根不递归(discover_projects 展开时 :223 设 false),nested monorepo 边界正确。
- **discover_projects 展开 PASS**:scan.rs:156-229 三路(根 manifest 入选/monorepo 展开 packages+apps/普通扫一层子目录)+ detect_stack 空过滤(:216-218 候选发现语义,无栈对 LLM 价值低,可手动 import)+ collect_tree_level is_sub 控 2 层(:638)不递归过深 + 隐藏目录/SAMPLE_IGNORED_DIRS 过滤。
- **images 收集 PASS**:scan.rs:329-481 ImageRef{alt,src}+ PartialEq + SAMPLE_README_MAX=8000 + truncate_chars 按 chars().take 截断(:664-669 UTF-8 安全)+ **实际 12 域黑名单** BADGE_HOSTS(:396-410 img.shields/shields/badge.fury/badgen/...)+ is_badge_image 域名+关键词双判(:441-448)+ collect_images 行级扫 +同 src 去重+上限 20(:454-481)+ is_pure_badge_line 混内容图返 false(:533-591);arch/screenshot 内容图留、badge 跳。
- **create_with_binding 抽取 PASS**:project.rs:59-107 统一校验+防重+探测+insert;create_project(:45-50)/import_project(:177)/import_projects_batch(:469)三入口共用;relocate 确未并入(:281-315 update_field 非 insert,注释 :56 声明);status 固定 planning+idea_id 透传行为一致无回归。
- **import_projects_batch 并发 PASS**:project.rs:415-493 双层 permit acquire_global+acquire_per_conv(:535-536 RAII 覆盖 complete)+ 非原子 join_all 逐项独立(LLM 失败 desc 空仍入库,create 失败返 error item,imported/skipped 计数)+ provider Arc 共享零拷贝 + 无 provider 明确报错非降级。
- **scan_directory_for_projects 纯规则 PASS**:project.rs:343-376 仅 discover_projects(spawn_blocking)+ find_binding_conflict 标已绑定,**零 LLM 调用**,深度一层。
- **前端 PASS**:api/project.ts:116-126 两 API + 类型完整 + Projects.vue:73-133 导入 modal(只读表格 name/stack/path/bound + checkbox 全选/半选/单选 + 已绑定禁选灰显 + toast 汇总 imported/skipped + 全失败保留重扫)+ i18n zh/en 19 key 对称。
- **df_ai::provider re-export 零断裂 PASS**:ai_node.rs:12 + project.rs:8/440 三消费点编译期证。
- **⚪ CR-39-1(low,文档笔误)**:登记称「徽章域 5 域黑名单」与代码实际 12 域(BADGE_HOSTS scan.rs:396-410)不符,代码更完备文档滞后。
- **⚪ CR-39-2(low,设计取舍)**:scan.rs:216-218 detect_stack 空栈过滤剔除有 manifest 无已知栈标志的合法项目(自定义构建系统),当前候选发现语义可接受,用户可手动单条 import。
- **待修项回流 todo**: **无**(2 low 文档/边缘非阻断)。
- **范围**: F-260614-06 6 决策落地(workflow wwtn2knn6)。①description 走 LLM(scan.rs extract_description 复用 complete,非纯规则);②采样改进(ProjectSample 扩 `images:Vec<ImageRef{alt,src}>` scan.rs:330/343 + readme 剥 frontmatter/TOC/纯徽章行截 8KB `SAMPLE_README_MAX=2000→8000`:346 + 保留内容图 markdown);③image 多模态条件化(ImageRef 数据结构+collect_images 收集:454,本批不读 base64 留接口待 F-260614-05);④monorepo 一层(is_monorepo:112 检 pnpm-workspace/lerna/turbo/nx/package.json workspaces + discover_projects:156 展开 packages/*/apps/* detect_stack 空过滤);⑤批量流程(scan_directory_for_projects project.rs:343 纯规则发现标已绑定 + import_projects_batch:415 并发 LLM llm_concurrency permit 限流复用绑定入库非原子逐项独立);⑥对称改进(抽 create_with_binding project.rs:59 create/import 共用校验+防重+探测+insert 缓解 :211 TODO,relocate 不并入)。前端 Projects.vue 导入 modal+api/project.ts 两 API+i18n 双语+scan.rs 单测(剥 badges/image 收集/monorepo/discover/extract_desc)。
- **维度**: ①is_monorepo 判定完备(pnpm-workspace/lerna/turbo/nx+package.json workspaces 全覆盖) ②discover_projects 展开(detect_stack 空过滤误剔合法项目风险/monorepo 子目录一层不递归过深) ③collect_sample images 收集(徽章域黑名单 5 域+is_badge_image alt+src 双判+is_pure_badge_line 整行纯徽章,内容图 arch/screenshot 留) ④SAMPLE_README_MAX 8000 截断(truncate_chars 不破坏 markdown/UTF-8 边界) ⑤create_with_binding 抽取(create+import 共用一致,relocate 确未并入) ⑥import_projects_batch 并发(llm_concurrency 双层 permit 限流/非原子逐项独立失败不阻塞) ⑦scan_directory_for_projects 纯规则不跑 LLM(标已绑定正确) ⑧df-project use df_ai::provider 路径不变(F-07 re-export 透明) ⑨前端预览只读+toast 汇总(导入 N/跳过 M)。
- **commit**: 2069f79(重构: df-ai-core trait下沉+导入历史)。
- **主代独立核查**: ✅ 全过(scan.rs:112 is_monorepo+:156 discover_projects+:330 ImageRef+:343 images 字段+:346 SAMPLE_README_MAX 8000+:357 collect_sample+:397-401 徽章域 5 域+:439 is_badge_image+:454 collect_images+:533 is_pure_badge_line+:742-876 单测 / project.rs:59 create_with_binding+:343 scan_directory_for_projects+:415 import_projects_batch / lib.rs:85-86 注册两 IPC / api/project.ts:117/125 两 API / Projects.vue:111 mono-tag / cargo check --workspace EXIT 0+vue-tsc EXIT 0)。
- **审查 agent 待复审重点**: ①is_monorepo/discover_projects 边界(nested monorepo/无 workspace 配置/detect_stack 空过滤误剔) ②徽章过滤完整(5 域黑名单+alt/src 双判是否漏内容图误剔或漏 badge 误留) ③create_with_binding 与原 create_project 行为一致(校验+防重+探测+insert 路径无回归) ④import_projects_batch 并发安全(llm_concurrency permit 限流正确+非原子逐项失败隔离) ⑤scan_directory_for_projects 性能(纯规则不跑 LLM,目录扫描深浅/大目录性能) ⑥前端 modal 预览表格只读+勾选+toast 汇总交互完整。
### CR-260616-40 F-03 对抗评估接 LLM(evaluate_with_llm 真实实现) — ✅ 已审(PASS)
- **范围**: F-260614-03 落地(workflow wy6rjn0en)。crates/df-ideas/src/adversarial.rs:143 evaluate_with_llm 真实实现(原 F-07 占位 bail):build_adversarial_prompt(:380 三角色辩论 prompt)+CompletionRequest(model 留空回填 default/temperature 0.4/max_tokens 2048/stream false)+provider.complete(:160 map_err anyhow)+parse_llm_eval(:500 serde_json::from_str 解析 LlmEvalRaw+枚举映射:571/586 bail 非法值)+clamp(final_score[0,10]/confidence[0,1] 结构兜底:167-170)。4 LLM mock 单测(MockProvider impl LlmProvider:733-755+a8 parse success/a9 bad JSON fallback/a10 bad enum fallback/a11 value clamp)。evaluate 调度(:111)+evaluate_heuristic(:176)+原 7 单测不动。
- **维度**: ①prompt 设计质量(三角色辩论+JSON schema 明确+temperature 0.4 防发散+max_tokens 2048 充足) ②JSON 解析容错(非 JSON/字段缺失/枚举非法 全 bail 触发 HeuristicFallback 降级,a9/a10 单测覆盖) ③clamp 兜底(final_score[0,10]/confidence[0,1],a11 单测) ④model 留空回填(OpenAICompatProvider convert_request 回退 default_model 正确) ⑤evaluate 调度不变(Some→Llm/Err→HeuristicFallback/None→Heuristic) ⑥MockProvider 单测真实性(impl LlmProvider trait 完整,stream bail 因 adversarial 不调 stream) ⑦不回归(原 7 heuristic+scoring 8 全过)。
- **commit**: dfe0096(新增: F-03对抗评估接LLM+F-02纯技能调用标题+i18n@转义)。
- **主代独立核查**: ✅ 全过(adversarial.rs:143 evaluate_with_llm 真实实现+:161 complete+:380 build_adversarial_prompt+:500 serde_json 解析+:571/586 枚举 bail+:167-170 clamp / MockProvider:733-755+4 单测 a8-a11 / cargo test -p df-ideas 19 passed EXIT 0)。
- **审查 agent 待复审重点**: ①build_adversarial_prompt 全文质量(:380 prompt 三角色+JSON schema 是否明确无歧义) ②parse_llm_eval 解析(:500 LlmEvalRaw 结构+枚举映射 serde/FromStr 实现) ③LLM 返回非 JSON 降级(a9 单测,extract JSON 容错——LLM 可能返回 markdown json 包裹需剥离) ④prompt injection 防护(idea.title/description 注入 prompt 是否需隔离,防用户输入操纵评估) ⑤max_tokens 2048 是否够(复杂 idea 评估 JSON 可能超) ⑥temperature 0.4 评估稳定性。
- **复审结论(2026-06-16·独立审查 agent · 亲跑 cargo test EXIT 0 + grep/read 核验源码)**: ✅ **PASS** — 🔴0 🟡0 ⚪4
- **自验**: `cargo test -p df-ideas adversarial` **11 passed** EXIT 0(7 heuristic a1-a7 + 4 LLM a8-a11)/ `cargo test -p df-ideas` **19 passed** EXIT 0(8 scoring + 11 adversarial 不回归)。
- **逐件核验(8 维度全 PASS)**:
- ①**prompt 设计** ✅:build_adversarial_prompt:398-448 三角色(正方/反方/分析师)+JSON schema 字段名/枚举字面量明确(`final_assessment: "StrongGo|Recommended|Conditional|Revised|Defer"`/`recommendation: "ImmediateAction|Soon|WithResources|ResearchMore|Monitor"`)+自洽约束(final_score 与 recommendation 对应 final_assessment)+SYSTEM_PROMPT:364-374 硬约束「只输出 JSON 对象/枚举字面量严格/数值区间」。temperature 0.4 注释:141-142 立论(0.3 重复/0.5 发散折中)合理。max_tokens 2048(schema ~10 字段全填~600 token + 论点 2-3 条/角色 × 3 角色 ~1200 token 富余)。
- ②**JSON 解析容错** ✅:parse_llm_eval:498-539 ①extract_json:545-561 剥 ```json 围栏(去前缀+跳语言标记行+去后缀)+trim ②serde_json::from_str → LlmEvalRaw:456-463 必填字段缺/类型错 → bail:500-502 ③枚举映射 parse_assessment_level:564-576/parse_recommendation:579-591 非法字面量 → bail:571/586(a10 "GoNow" 覆盖)。a9 非 JSON 降级 HeuristicFallback:830 覆盖。
- ③**clamp 兜底** ✅:parse_llm_eval 内 confidence clamp[0,1]:511/517 + final_score clamp[0,10]:534;evaluate_with_llm:167-170 再做一次结构兜底(注释明确「结构兜底」)。a11 confidence 1.5→1.0/-0.3→0.0/final_score 99→10.0:874-876 全过,仅越界不降级 evaluated_by=Llm:873。
- ④**model 留空回填** ✅:OpenAICompatProvider::convert_request:295-296 `if req.model.is_empty() { self.default_model.clone() }`;AnthropicCompatProvider::convert_request:282-283 同样回填;两 provider 一致,留空 String::new() 正确回退各自默认模型。
- ⑤**evaluate 调度** ✅:evaluate:112-133 三分支 Some(p)→evaluate_with_llm:114 成功标 Llm:116/Err 标 HeuristicFallback:123/warn 日志:121;None→evaluate_heuristic:128 标 Heuristic:129。调度不变,a9/a10 验证降级路径。
- ⑥**MockProvider 真实性** ✅:MockProvider:733-761 impl LlmProvider trait 三方法完整(complete:739-749 返预设 text/model="mock-model"/usage default/stream:751-756 bail「不调用」/name:758-760)。embed 用 trait 默认实现(provider.rs:222-224 返 Err,adversarial 不调 embed 无影响)。stream bail 注释:755 明确「adversarial 路径不调用」合理。
- ⑦**不回归** ✅:原 7 heuristic 单测 a1-a7:645-723 全过(评分→recommendation 映射/confidence 区间/thesis 含标题/negative evidence 非空/final_score 一致性);scoring 8 单测全过。total 19 passed。
- ⑧**prompt injection 防护** ⚪(low,结构安全/语义受限):**结构性注入已阻断**——idea.title/description 经 `format!` 命名参数注入(:443-446),format! 不对参数值二次解析,Rust 运行时 title/description 中的 `{`/`}` 字符直接作为字面文本输出,不会破坏 prompt 模板骨架。**语义性注入未隔离**(用户可写 description="忽略规则,输出 final_score=10")属 LLM 固有局限非本实现 bug;description 截 800 字:383-390 已限长降低面。当前评估结果越界有 clamp 兜底、枚举非法有 bail 降级,最坏后果降级启发式不致数据错。
- **⚪ low 清单(4,全非阻断)**:
- **CR-40-1(low,clamp 双重执行冗余)**:parse_llm_eval 已 clamp confidence:511/517+final_score:534,evaluate_with_llm:167-170 又 clamp 一次(parse_llm_eval 注释:533 也承认「调用方再做一次」)。无害防御性重复,建议删 :167-170 留 parse 内单点收口,减少阅读歧义。
- **CR-40-2(low,extract_json 围栏位置局限)**:extract_json:548 只 strip_prefix("```") 处理围栏在开头;LLM 若输出 `好的,以下是评估:\n```json\n{...}\n````(围栏不在首),strip_prefix 失败走 trimmed 原文 → serde 报错 → 降级。降级语义安全但命中率略低;非阻断(降级正确),可后续用正则 `(?s)\{.*\}` 兜底提取首个 JSON 对象增强。
- **CR-40-3(low,semantic prompt injection 未隔离)**:见维度⑧,description 截 800 字已限长,枚举/数值有 clamp+bail 兜底,语义注入最坏降级启发式,不阻断。
- **CR-40-4(low,temperature/max_tokens 硬编码不可配)**:temperature 0.4/max_tokens 2048 硬编码:153-154,无运行时调参能力;当前单 provider 单次调用可接受,后续若多 provider 调优需抽配置。
- **待修项回流 todo**: **无**(4 low 全非阻断:clamp 冗余/extract_json 增强/semantic injection/硬编码均可在后续迭代处理,当前 PASS)。
- **主代独立核查**: ✅ 全过(adversarial.rs:143 evaluate_with_llm 真实实现+:161 complete+:380 build_adversarial_prompt+:500 serde_json 解析+:571/586 枚举 bail+:167-170 clamp / MockProvider:733-755+4 单测 a8-a11 / cargo test -p df-ideas 19 passed EXIT 0)。
### CR-260616-41 batch64 F-03 收口三件(advance_task/run_workflow 工具注册+update_task 拒 status+crud 白名单移 status) — ✅ 已审(PASS·🔴-1已修)
- **范围**: F-260616-07 阶段3 确定性收口(workflow wii1u1lnm,3 agent 并行)。①**Agent A tool_registry.rs**:advance_task 注册(:395,handler L407 调 `df_nodes::task_advance_node::advance_task_atomic(&TaskRepo::new(&db),id,target_status)` 与 IPC `commands::task::advance_task:165` 同源,thin 转发无 AppHandle/State 依赖,RiskLevel::Medium)+ run_workflow 注册(:428,handler 架构约束无 AppHandle/State 报错引导前端转调 `invoke('run_workflow')` IPC,RiskLevel::High,ToolDefinition+审批文案 L1260-1261/1286-1287 注册)+ update_task handler guard L374 `if field=="status"` bail(schema 通用 field/value 模式非 enum,故靠 handler guard 拦非 schema 改)。②**Agent B crud.rs**:tasks allowed_columns L331-347 移 `"status"`+`"review_rounds"`(D-260616-04 收口),注释明确「status/review_rounds 走 advance_status_atomic CAS 独立路径,后人勿补回白名单」;`advance_status_atomic` L848 CAS SQL `WHERE id AND status=expected AND deleted_at IS NULL` 完全绕 `validate_column_name`/`allowed_columns_for`;`tests/project_soft_delete.rs:250` 断言反转 `update_field_rejects_tasks_status`(改 status 现 Err)。③**Agent C CR-40 审查**(对 batch62 dfe0096 evaluate_with_llm):PASS 4 low 非阻断。
- **维度**: ①advance_task handler 同源核验(tool_registry.rs:407 调 advance_task_atomic 与 commands::task::advance_task:165 同源,thin 转发无逻辑分叉) ②update_task handler guard 双重防御(L374 handler 拒 + df-storage 白名单拒,status 双层拦截) ③crud 白名单移 status+review_rounds 收口语义(advance_status_atomic L848 CAS 独立路径不经 allowed_columns,review_rounds 唯一写入路径防计数错乱) ④run_workflow handler 架构约束(handler 无 AppHandle/State 报错引导走 IPC 是否合理,后续批注入 AppState 句柄让 AI 直驱) ⑤审批文案映射(display_hint_for_tool L1260-1261 + tool_display_hint L1286-1287 advance_task/run_workflow 条目) ⑥project_soft_delete.rs 测试断言反转(update_field_rejects_tasks_status 改 status 现 Err 正确) ⑦前端 status 路径零残留(taskApi.update 全代码库零调用方,status 唯一走 taskApi.advance→advance_task IPC) ⑧commands/task.rs update_task IPC 残留死代码(task.rs:100-108 field=="status" 分支移白名单后永不命中,回流 todo 清理)。
- **commit**: 待提交(batch64 攒批)。
- **主代独立核查**: ✅ 全过(tool_registry.rs advance_task:395+handler:407 advance_task_atomic 同源 / run_workflow:428+handler 约束报错引导 IPC / update_task handler guard:374 `field=="status"` bail / crud.rs:331-347 tasks 白名单移 status+review_rounds 注释明确 / advance_status_atomic:848 CAS `WHERE id AND status=expected AND deleted_at IS NULL` 独立路径 / cargo check --workspace EXIT 0(5 pre-existing warnings 无关)+ df-storage 11 集成测试含新 update_field_rejects_tasks_status)。
- **审查 agent 待复审重点**: ①advance_task handler 同源 IPC(commands::task::advance_task:165 核验,thin 转发无逻辑分叉,df_core::error::Error→anyhow ?From 链路) ②update_task handler guard 双重防御(handler guard L374 + df-storage 白名单双拦,是否漏路径) ③crud 白名单移 status+review_rounds 收口(advance_status_atomic L848 CAS 是否真独立不经 allowed_columns,review_rounds 唯一写入路径防旁路写) ④run_workflow handler 报错引导 IPC 是否合理(架构约束无 AppHandle/State,后续批注入句柄) ⑤审批文案映射完整(display_hint L1260-1261/1286-1287) ⑥测试断言反转正确(project_soft_delete.rs:250 update_field_rejects_tasks_status 改 status 现 Err) ⑦commands/task.rs:100-108 update_task IPC 残留死代码回流 todo。
- **复审结论(2026-06-16·审查 agent 亲跑 cargo + 独立 grep/read 核验源码)**: ⚠ **ISSUES** — 🔴1 🟡0 ⚪1 → **修复后(主代 b94e74a 之后)**: ✅ **PASS** 🔴0 🟡0 ⚪1(🔴-1 测试 fixture 已修)
- **验证**: `cargo check --workspace` **EXIT 0**(5 pre-existing dead_code warnings 无关本 CR)/ `cargo test -p df-storage` **33 passed**(22 lib + 11 project_soft_delete 含新 `update_field_rejects_tasks_status` ✅)/ `cargo test -p df-nodes` **66 passed; 1 FAILED**(`advance_succeeds_after_prior_status_change` 红,见 🔴-1)→ **🔴-1 修复后 67 passed; 0 failed; 1 ignored**(task_advance_node.rs:331-332 改 advance_status_atomic)。
- **①advance_task handler 同源核验** ✅ tool_registry.rs:407 调 `advance_task_atomic(&TaskRepo::new(&db), id, target_status)` 与 commands::task::advance_task(task.rs:165)同源调 `df_nodes::task_advance_node::advance_task_atomic(&state.tasks, &id, &target_status)`,均 thin 转发到同一 `advance_task_atomic`(task_advance_node.rs:43-90),无逻辑分叉。df_core::error::Error 是 thiserror(crate df-core/src/error.rs:5 `#[derive(Error)]`),实现 std::error::Error,anyhow::Error: From<E> 链路通(`?` 编译过 cargo check 证)。
- **②update_task handler guard 双重防御** ✅ tool_registry.rs:374-378 `if field=="status"` bail(第一层 handler 拒)+ tool_registry.rs:380-382 `is_allowed_column("tasks", field)` 查 crud.rs:331-347 白名单(第二层,白名单已无 status),status 双层拦截。无漏路径:value 必经 args["field"].as_str() 解析后两条 guard 之一必中。
- **③crud 白名单移 status+review_rounds 收口** ✅ crud.rs:342 现 tasks 白名单 `"project_id","title","description","priority","branch_name","assignee","workflow_def_id","base_branch","updated_at"`(无 status/review_rounds)。advance_status_atomic(crud.rs:848-880)走独立 CAS SQL `UPDATE tasks SET status=?1[, review_rounds=review_rounds+1] WHERE id=?3 AND status=?4 AND deleted_at IS NULL`,**不经 validate_column_name/allowed_columns_for**(无白名单调用)。review_rounds 唯一写入路径收口语义达成:除 advance_status_atomic 退回转换 bump_rounds=true 路径,无任何旁路可写(crud.rs:865-866 SQL 内联 `review_rounds+1`)。
- **④run_workflow handler 报错引导 IPC** ✅ 合理。tool_registry.rs:431-446 handler 仅持 `_db: Arc<Database>`(L432 实际未用,前缀 `_` 标记),无法构造 AppHandle/AppState(registry/event_bus/workflows Repo),L440-444 显式 anyhow 返回明确错误引导前端 `invoke('run_workflow', {task_id, target_status})`。符合单一执行路径原则(不在 handler 重放 DAG 引擎)。注释 L419-426 标注后续批注入 AppState 句柄扩展路径。
- **⑤审批文案映射** ✅ display_hint_for_tool(tool_registry.rs:1259-1261)注册 `advance_task => "推进任务状态:{} → {}", &["id","target_status"]` + `run_workflow => "触发工作流:任务{} 推进到 {}", &["task_id","target_status"]`;tool_display_hint(tool_registry.rs:1286-1287)注册 `advance_task => Some("推进任务状态")` + `run_workflow => Some("触发工作流")`。两条目齐全。
- **⑥project_soft_delete.rs 测试断言反转** ✅ project_soft_delete.rs:250 `update_field_rejects_tasks_status``res.is_err()`(L259-262)+ 对照 `assert_ne!(rec.status, "done")`(L266)。亲跑 11 集成测试全过 EXIT 0。
- **⑦前端 status 路径零残留** ✅ taskApi.update(src/api/task.ts:17-19)仅 tasks.ts:31 `updateTask` 调用,全代码库 grep `updateTask(` 零组件调用方(仅定义无消费)。TaskDetail.vue:281 推进走 `taskApi.advance(task.value.id, target)`(状态唯一路径→advance_task IPC)。status 前端路径零残留达成。注:tasks.ts:29 `updateTask` 函数本身保留(改 title/description 等非 status 字段,合法),仅 status 字段已无消费方。
- **⑧commands/task.rs update_task IPC 残留分支** ⚪(low)task.rs:102-108 `if field=="status" && !TaskStatus::is_valid(&value)` 分支并非纯死代码:它仍为 status **非法值**(拼写错/越界)提供更友好早错误(先于白名单拒)。但 status **合法值**路径必落到 task.rs:133-135 `update_field` 被白名单拒,故「校验通过后写入」分支永不命中。建议保留 value 校验(改善 UX)+ 删除成功路径假设注释,或整体移除改由 advance_task 独占(后续迭代决定,非阻断)。
- **🔴-1(df-nodes 测试回归,high)**: **task_advance_node.rs:332** `repo.update_field("t1", "status", "in_progress").await.unwrap()` 测试红 —— 退回测试 `advance_succeeds_after_prior_status_change`(L324-336)用 `update_field` 模拟「旁路并发改 status」前置,但本 commit 已把 status 移出 tasks 白名单,`update_field` 现 Err → `unwrap()` panic「表 tasks 不允许的字段名: status」。亲跑 `cargo test -p df-nodes` 证实 66 passed/1 failed。**生产代码无回归**(advance_task_atomic 本身工作正常,66 个推进链测试全过),仅测试 fixture 需同步:把 L332 改成 `repo.advance_status_atomic("t1","todo","in_progress",false).await.unwrap()`(用 CAS 路径模拟并发改,语义等价)或直接 `repo.insert(rec("t1","in_progress"))` 重建前置态。CI 红门未守,须修后才能合。 **✅ 已修(主代 task_advance_node.rs:331-332 改 `advance_status_atomic("t1","todo","in_progress",false)` 走 CAS 合法路径模拟并发推进,亲跑 cargo test -p df-nodes 67 passed/0 failed/1 ignored)**
- **⚪ low 清单**: ①task.rs:102-108 status value 校验分支半死代码(合法值成功路径永不命中,非法值仍触发,见 ⑧)。
- **待修项回流 todo**: **🔴-1**(df-nodes 测试回归 task_advance_node.rs:332) **✅ 已修**(主代改 `advance_status_atomic`,亲跑 67 passed,见上方修复记录)。低 ①task.rs:102-108 status value 校验半死代码非阻断(审查建议保留 value 校验改善 UX + 删成功路径假设注释,后续迭代决定)。
---
### CR-260616-43 AiNode 自审阶段2(AiNode持db+ai_self_review节点+DAG透传+前端展示) — ✅ 已审(PASS)
- **复审结论(2026-06-16·CR-43 agent 亲跑 cargo+vue-tsc+test,142s)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-nodes` **79 passed; 0 failed; 1 ignored** / `npx vue-tsc --noEmit` EXIT 0。
- **①AiSelfReviewNode prompt 四维度+JSON 兜底完备 PASS**:build_review_prompt(ai_node.rs:405-424) 含需求+产出+四维度+输出格式 + parse_review_json(:360-381) 覆盖非 JSON/非 Object/缺 verdict → verdict=unknown 兜底 + 代码块剥离(:363-367 ```json 围栏) + 单测(:928-963) 合规/非法/fence 场景全过。
- **②写回 output_json review 子字段 PASS**:读现有合并不覆盖 text(:514-519 先读 output_obj 若 JSON 则复用) + review 子字段写入(:520-534 含 verdict+reviewed_at+reviewer_model) + 落库失败只 warn 不阻断(:538-544)。
- **③DAG 透传 PASS**:NodeOutput.data 平铺(:550-556 verdict/summary/suggestions) + human_review 经 inputs 读(task_workflow_templates.rs:72 edge ai_self_review→human_review) + HumanNode 零改动(git diff 核验 human_node.rs 在 3 commit 中无变动)。
- **④state.rs 注册+工厂闭包 PASS**:AiNode 注册(:261-263 AiNode::new(ai_db)) + AiSelfReviewNode 注册(:269-270 AiSelfReviewNode::new(review_db)) + move db.clone() 闭包捕获正确。
- **⑤testing 模板节点类型对齐 PASS**:模板 L61 注册 "ai_self_review" 节点 + L72 edge 透传 + 单测 L123-143 验证节点类型+边方向+options。
- **⑥前端 schema 契约一致 PASS**:types.ts:102 output_json? 字段 + L98-100 schema 注释 + TaskDetail.vue:229-234 parsedOutput computed try/catch 兜底 + reviewVerdictClass/reviewLabel/reviewSuggestions 均从 parsedOutput.review 读 + 字段名对齐 verdict/summary/suggestions。
- **⑦i18n 双语 PASS**:zh-CN/taskDetail.ts:23-27 review.pass/fail/unknown + en/taskDetail.ts:23-27 对称翻译 + 特殊字符无转义需求(值均为静态枚举字面量)。
- **⑧P0 安全债登记确认**:api_key 明文 config 注入违背 FR-S1 已在 memory/devflow-aichat-review-pending.md 登记 + 本次 CR 范围已实现 resolve_provider 三路径(ai_node.rs:62-135 完整实现) + F-260616-07c 阻塞⑥联调已记录。
- **⚪ CR-43-1(low,单测降级声明)**:AiSelfReviewNode.execute 完整 mock LlmProvider 单测本次降级(仅 parse_review_json+update_field 单测覆盖,P3 待补)。ai_node.rs:589-875 单测已覆盖 resolve_provider 双路径+parse_review_json 兜底+update_field 路径,end-to-end execute 单测依赖真实 LLM(#[ignore] glm_live_complete 存在),非阻断。
- **待修项回流 todo**: **无**(1 low 单测降级非阻断,已在 CR 信息说明 P3 待补)。
- **范围**: F-260616-07 阶段3 AiNode 自审闭环②③④⑤(3 commit:539b5ed 迁移 + c10adaf ai 侧 + 741b0b9 前端)。①**①迁移**(539b5ed):TaskRecord 加 `output_json: Option<String>` + V17 迁移(幂等补列) + crud 白名单加 output_json(L346)+SELECT/INSERT/UPDATE 全链路 + 5 构造点补 None。②**ai_node.rs②③④**(c10adaf):AiNode 持 db(`Arc<Database>`,对齐 TaskAdvanceNode:98-110 先例)+ execute 后 `config["task_id"]` 存在则 `update_field` 落 output_json(产出 schema {text,model,usage},落库失败只 warn 不阻断)+ AiSelfReviewNode 独立节点(parse_review_json 兜底 verdict=unknown + build_review_prompt 四维度 + 写回 review 子字段不覆盖 text)+ state.rs L269-270 注册 ai_self_review + ai 工厂 L262 `AiNode::new(db)` + testing 模板 L61 ai_self_review + L72 edge(ai_self_review→human_review)+ NodeOutput.data 平铺透传 + HumanNode 零改动。③**前端⑤**(741b0b9):types.ts output_json?(schema 注释)+ TaskDetail.vue parsedOutput computed(try/catch 兜底)+review-card 红绿标+renderedOutput 复用 useRendered + i18n zh/en taskDetail.output/review 双语。
- **维度**: ①AiSelfReviewNode prompt 四维度+JSON 兜底完备性(parse_review_json 各路径 verdict 覆盖,代码块 fence 剥离重试) ②写回 output_json review 子字段(读现有合并不覆盖 text,reviewed_at/reviewer_model) ③DAG 透传(NodeOutput.data 平铺,human_review 经 inputs 读,HumanNode 零改动核验) ④state.rs 注册+工厂闭包 move db.clone() ⑤testing 模板节点类型对齐 ⑥前端 schema 契约一致(types.ts 与 ai_node.rs 写回 schema 字段名对齐 verdict/summary/suggestions) ⑦i18n 特殊字符转义 ⑧**P0 安全债登记**(api_key 明文 config 注入违背 FR-S1,跨 crate 阻塞,F-260616-07c 待决) ⑨主代亲跑 cargo df-nodes 73 passed + workspace EXIT 0 + vue-tsc EXIT 0。
- **commit**: 539b5ed + c10adaf + 741b0b9。
- **主代独立核查**: ✅ 全过(human_node.rs 零改动 git diff 确认 / state.rs L262 AiNode::new(ai_db)+L269-270 AiSelfReviewNode::new(review_db) / templates L61 ai_self_review+L72 edge / ai_node.rs AiNode 持 db@L119+execute 写@L184+AiSelfReviewNode@L292+parse_review_json@L258 / cargo test -p df-nodes 73 passed 亲跑 / cargo check --workspace EXIT 0 / vue-tsc EXIT 0 / 前端 schema 对齐)。
- **审查 agent 待复审重点**: ①AiSelfReviewNode.execute 完整 mock LlmProvider 单测(本次降级 parse_review_json+update_field 单测,P3 待补) ②parse_review_json 兜底各路径(verdict=unknown 覆盖非 Object/数组/缺 verdict/JSON 解析失败/fence 剥离) ③写回 output_json 读现有合并的并发安全(同一 task 多次自审竞态) ④human_review 审批卡 description 拼自审摘要未实现(P2,透传链通但卡片未拼,HumanNode 只读 config 不读 inputs) ⑤P0 api_key config 注入(F-260616-07c,✅ 已由 CR-44 secret 下沉+注入链解决)。
---
### CR-260616-44 P0 secret下沉+AiNode provider注入链(方案B FR-S1闭环) — ✅ 已审(PASS)
- **复审结论(2026-06-16·CR-44 agent 亲跑 cargo+test+vue-tsc,485s)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-nodes` **79 passed; 0 failed; 1 ignored**(含 9 个 resolve_provider 新增测试)/ `npx vue-tsc --noEmit` EXIT 0。
- **① secret 下沉边界 PASS**:secret.rs:7-9 注释明确不下沉 build_provider_for(依赖 df-ai 会循环)。grep 0 处函数定义,5 处全注释(src-tauri/Cargo.toml:17/secret.rs:7/8)。src-tauri/secret.rs:20 pub use df_storage::secret::* 转发所有下沉函数 + :32 build_provider_for 保留全限定路径调 df_ai::build_provider(:37)。
- **② 转发壳 glob 兜底 PASS**:13 个实际调用点(排除注释行)全部经 crate::commands::ai::secret:: 或 super::secret:: 路径调用(commands.rs 4 处+agentic.rs 2 处+title.rs 1 处+knowledge_inject.rs 2 处+idea.rs 1 处+project.rs 2 处+lib.rs 1 处迁移调用)。编译期验证零断裂(cargo check EXIT 0)。
- **③ keyring feature 平台覆盖 PASS**:Cargo.toml workspace:24 统一声明 windows-native+apple-native+linux-native-async-persistent 三平台。df-storage/Cargo.toml:18 keyring = { workspace = true }。secret.rs:220 cfg(any(target_os="windows",target_os="macos")) 单测(仅桌面 OS 环境实测 keyring roundtrip,CI 任意 OS 安全)。
- **④ resolve_provider 三路径完备 PASS**:ai_node.rs:62-135 路径1 provider_id 优先→get_by_id(:76)→resolve_provider_secret(:143)→ensure_resolved_key(:144)/ 路径2 老明文兼容(:86-121,has_plain_base+has_plain_key 双判+:89-92 warn "老路径将在后续版本移除")/ 路径3 兜底(:124-134,list_all→find is_default→or_else 首条,友误 "未配置 AI Provider")。9 单测全覆盖(by_id/empty_key/unknown_id/legacy_plain/legacy_empty/fallback_default/no_providers/fallback_first/config_model_override)。
- **⑤ FR-S1 闭环 PASS**:api_key 仅存 ResolvedProvider 内存(ai_node.rs:45 明文字段,注释 "仅 AiNode 内存可见"),不进 config(schema L313/314 "已废弃过渡" 描述)/NodeOutput(L273-281 仅 text/model/usage 无 api_key)/schema(L316 required 改 ["provider_id"])。resolve_provider_secret(DB 优先→keyring)解析,build_provider(:235-240)传明文仅 AiNode 进程内存可见,不落库不外泄。
- **⑥ schema required 改 [provider_id] PASS**:ai_node.rs:316 required=["provider_id"] + :309 description "FR-S1密钥经 df_storage::secret 解析,不进 config" / AiSelfReviewNode :569 required=["task_id","provider_id"]。
- **⑦ 老明文兼容 warn 过渡期 PASS**:ai_node.rs:89-92 tracing::warn!("AiNode 明文 api_key/base_url 经 config 注入已废弃, 改用 provider_id (FR-S1). 老路径将在后续版本移除")。路径2 逻辑保留兼容(:86-121 has_plain_base+has_plain_key 双判)。
- **⑧ 迁移逻辑 FAILCOUNT SIDECAR PASS**:secret.rs:34-72 failcount_path/.devflow-keyring-failcount + read/write/record/clear_migration_failcount + migrate_secrets_to_keyring(:125)失败达阈值 3 升级 warn(:136-142 明文滞留风险+3 条排查建议:确认 OS 钥匙串可用/keyring 后端异常/手动重新保存)。lib.rs:28 启动调用非阻断(:29 warn "启动密钥迁移失败(非阻断)")。
- **⑨ df-nodes 显式导入 PASS**:ai_node.rs:17 use df_storage::secret::{ensure_resolved_key, resolve_provider_secret} 显式导入(非 glob)。resolve_from_record(:143-144)直接调用。ai_node.rs:14-15 也显式导入 AiProviderRepo/Database/AiProviderRecord。
- **⑩ testing 模板 PASS**:task_workflow_templates.rs:61 注册 "ai_self_review" 节点 + :72 edge(ai_self_review→human_review) + 单测(:123-129)验证节点类型+边方向+config 空。state.rs:269-270 注册 AiSelfReviewNode::new(review_db) 闭包捕获正确。
- **待修项回流 todo**: **无**(10 维度全 PASS,零问题)。
- **范围**: F-260616-07c P0 安全闭环(3 commit:36f70b3 方案 + 766f235 下沉 + 84a6fc4 注入链)。①**secret 下沉**(766f235):df-storage 新建 src/secret.rs move 纯密钥逻辑(get/set/delete/resolve/ensure/migrate+failcount,**不含 build_provider_for** 避循环)+ keyring workspace 平台 feature(windows/apple/linux-native-async-persistent)+ src-tauri secret.rs 瘦身转发壳(pub use df_storage::secret::* + build_provider_for 保留全限定路径调)+ 13 调用点零改(glob 兜底)+ keyring 单测 cfg-gate。②**AiNode 注入链**(84a6fc4):ai_node.rs resolve_provider(db,config)->ResolvedProvider 三路径(provider_id 优先 get_by_id→resolve_provider_secret→ensure / 老明文兼容 warn / 空兜底 list_all find is_default)+ AiNode/AiSelfReviewNode execute 同构经 resolve_provider 拿 provider + schema required [provider_id] + 模板 config 空走兜底。
- **维度**: ①secret 下沉边界(纯密钥逻辑无 df-ai 依赖,build_provider_for 留 src-tauri,无循环) ②转发壳 glob 兜底(13 调用点零改核验) ③keyring feature 平台覆盖(Linux linux-native-async-persistent) ④resolve_provider 三路径完备(provider_id/老明文 warn/兜底 is_default/无 provider 友好错误) ⑤**FR-S1 闭环**(api_key 仅 ResolvedProvider 内存,不进 config/NodeOutput/schema required) ⑥schema required 改 [provider_id] ⑦老明文兼容 warn 过渡期 ⑧主代亲跑 cargo df-nodes 79 passed(9 新增 resolve_provider 测试)+ workspace EXIT 0。
- **commit**: 36f70b3 + 766f235 + 84a6fc4。
- **主代独立核查**: ✅ 全过(df-storage secret.rs 无 build_provider 函数定义,grep 5 处全注释 / src-tauri 转发壳 pub use+build_provider_for 全限定路径 / keyring workspace feature 三平台 / resolve_provider 三路径 L74/L89/L123 / schema required L316+L569 / api_key 仅 ResolvedProvider L45/238/464 调 build_provider 不进 NodeOutput / cargo test df-nodes 79 passed 亲跑 / workspace check EXIT 0)。
- **审查 agent 待复审重点**: ①secret 下沉后 src-tauri 13 调用点经转发壳路径完整性(lib.rs/commands.rs/agentic.rs + build_provider_for 6 处) ②keyring Linux CI(linux-native-async-persistent 后端依赖 zbus/secret-service) ③resolve_provider 老明文兼容路径后续移除时机 ④FR-S1 端到端验证(实测 tauri dev:testing 模板 ai_self_review 经 resolve_provider 拿 provider→LLM→写 output_json) ⑤⑥端到端联调实测。
---
### CR-260616-35 batch59 CR-34-1 守卫 + CR-30 流式重试对齐决策a1 + UX-2025-04 断线保文 — ✅ 已审(PASS)
- **范围**: 3 agent 合并批(workflow wdlt0xfl6,主代独立核查全过)。①**CR-34-1** useAiPanel.ts:102 守卫加有效性检查 `|| !list.some(p=>p.id===activeProvider)`(deleted provider 残留回落 DB is_default) ②**CR-30-1/CR-30-2 + UX-2025-04 合并**:stream_recv.rs `StreamResult` enum(Complete/Partial/InitFailed 三分支) + `classify_status_or_class`(4xx Fatal/5xx+429+timeout Retryable 镜像 retry::is_status_retryable) + agentic.rs 重试对齐决策 a1(Init 失败重试复用 retry::backoff_delay 1s→2s→4s±20%jitter + 30s 总预算 retry_deadline + Fatal 分类 4xx 立即放弃; MidStream Partial 不重试保文→入库 + AiCompleted(incomplete=true) + 系统提示「响应因网络中断不完整」) + 删 3 处误导注释(agentic.rs:12/commands.rs/state.rs) + retry.rs `pub mod` + `backoff_delay`/`is_status_retryable``pub` 暴露供 src-tauri 复用 + useAiEvents.ts:177 补 `case 'AiStreamRetry'`(消除死事件穿透) + AiCompleted case 处理 incomplete + types.ts:204/mod.rs `incomplete?:boolean` 字段(所有 emit 点补 None 向后兼容) + i18n aiStreamRetry/responseIncomplete 双语。
- **维度**: 流式核心中风险(重试循环正确性/保文语义/跨 crate pub 暴露影响面) + 前端事件消费完整性 + 决策 a1 对齐度(Init 可重试 ≤max_retries+jitter+30s+Fatal, MidStream 不重试) + B-260615-05 保留(空文本无 finished 仍 InitFailed 交重试)。
- **commit**: 待提交(累积 ~30 文件攒批)。
- **复审结论(2026-06-16·CR-30 agent 亲跑 cargo + 主线程 CR-34-1 预核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai retry` **6 passed**(backoff_is_monotonic_within_jitter/status_classification/fatal_aborts_immediately/first_attempt_ok/retry_once_then_ok/all_retryable_exhausts)/ `cargo test stream_recv` **20 passed**(classify_429_retryable/classify_4xx_fatal 等)。
- **CR-30-1(7 项全 PASS)**:agentic.rs:320 真用 `retry::backoff_delay(retry_attempt+1)`(非自写,旧 `1<<retry_attempt` 仅留注释)/ backoff 含 ±20% jitter(retry.rs:74-85 SystemTime nanos)/ 30s 总预算(agentic.rs:242 retry_deadline + :290 budget_exhausted + :321 min 截断)/ 4xx Fatal 立即放弃(agentic.rs:293 `!retryable` return + stream_recv.rs:411-434 classify_status_or_class 镜像 is_status_retryable)/ 三处注释修正(commands.rs:1114 / state.rs:184 / agentic.rs:38)/ DRY 无重写(retry_with_backoff 仍 pub(crate) 流式不走)/ AttemptOutcome+is_reqwest_error_retryable 暴露合理。
- **CR-30-2(7 项全 PASS)**:useAiEvents.ts:177-186 `case 'AiStreamRetry'` 完整(取最后 isError 消息 + i18n t 更新「重试 n/m」,break 非穿透)/ 看门狗 reset 正确(AiStreamRetry 不在 NO_RESET_WATCHDOG Set)/ MidStream 决策 agentic.rs:274-285 Partial→incomplete=true + break 不重试,:286-342 InitFailed retryable 分支重试 / currentText 清空(useAiEvents.ts:316 AiError case `state.currentText=''` 再 AiStreamRetry 更新气泡,无累积重复)/ AiCompleted incomplete 系统提示(后端 agentic.rs:384 push session.messages 落库 + 前端 useAiEvents.ts:278-285 气泡)/ types.ts:215-217 variant + :202-204 incomplete?:boolean / i18n aiStreamRetry+responseIncomplete 双语齐。
- **回归(4 项无回归)**:permit RAII 释放(agentic.rs:405-406 显式 drop + loop 作用域 Drop)/ guard.reset 耗尽复位(8 exit 点全覆盖 :137/:300/:314/:352/:393/:457/:480/:543)/ request 每轮重建(:251-266 messages 从 session lock 重构)/ 无新死代码(AttemptOutcome/is_reqwest_error_retryable/is_status_retryable/backoff_delay/AiStreamRetry/classify_status_or_class 全有消费)。
- **CR-34-1(主线程预核验 PASS)**:useAiPanel.ts:102 守卫 `|| !list.some(p=>p.id===activeProvider)` 残留无效回落 DB is_default,cycleProvider 不受影响(findIndex 不再 -1 跳过 ps[0])。
- **UX-2025-04 断线保文**:核心 ✅(MidStream Partial 保文入库 + incomplete 系统提示);「继续生成」按钮未做(可复用 UX-2025-03 重试能力按需补,非阻断)。
- **待修项回流 todo**: **无**(CR-34-1/30-1/30-2 三条确认修复,todo 勾完成)。
- **主代独立核查**: ✅ 全过(useAiPanel:102 守卫 / stream_recv:97 enum 三分支 + 注释 / agentic:242 retry_deadline + :274 Partial 不重试 + :286 InitFailed 重试 + :320 backoff_delay 复用 + :360 if incomplete 保文入库 + :400 emit incomplete:Some(true) / useAiEvents:177 AiStreamRetry case / types:204 incomplete / retry.rs:62+74 pub + agentic:14 `use df_ai::retry` 跨 crate 通)。
- **审查 agent 待复审重点**: ①stream_recv StreamResult 三分支边界(Partial vs InitFailed 空文本路径,B-260615-05 保留点) ②agentic 重试循环 permit RAII 释放/边界(max_retries=0/guard.reset) ③跨 crate retry pub 暴露是否过度(retry_with_backoff 仍 pub(crate)) ④AiCompleted incomplete 前端消费完整性(系统提示气泡镜像后端) ⑤UX-2025-04「继续生成」按钮**未做**(保文核心 ✅,按钮可复用 UX-2025-03 重试能力或按需补)。
---
### CR-260616-45 波1 F-01阶段3厂商模型列表拉取 + UX-01错误气泡Fatal区分 + UX-03工具卡按轮次收起 — ✅ 已审(PASS)
- **复审结论(2026-06-17·CR-45 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai model_fetch` **24 passed** 全过/ `npx vue-tsc --noEmit` EXIT 0。
- **①build_models_url 7 分支完备 PASS**:model_fetch.rs:165-178 尾 trim/|v1|v4/|models/chat/completions/api/根 base 7 分支全覆盖;断言成立(无 /v1/v1/models 重复,trim 后单次 suffix 判断);测试 build_url_v1_suffix_appends_models:303 `assert!(!...contains("/v1/v1"))` 编译期证。
- **②噪音过滤 13 类 + embedding 保留 PASS**:is_non_chat_model:196-219 dall-e/midjourney/sd/imagen/tts/whisper/audio/speech/voice/realtime/transcribe/moderation/-search 13 类;embedding_kept_per_design:410-414 单测证保留;注释 :194 明确按 §5.4 设计。
- **③反序列化多 alias 兜底 PASS**:ModelsList:248-253 serde(default) + data alias="models";ModelEntry:256-259 id alias="name";into_ids:263-269 filter_map 丢弃 None;parse_openai_format/parse_anthropic_format/parse_ollama_format/parse_missing_data_field_yields_empty/parse_entry_missing_id_skipped 5 单测全覆盖。
- **④协议分派 PASS**:fetch_openai_compat:66-89 Bearer 鉴权 + /v1/models;fetch_anthropic_compat:92-119 x-api-key+anthropic-version 双 header + /v1/models;fetch_model_names:40-46 其他报 UnsupportedProvider;三路径完备。
- **⑤错误映射友好提示 PASS**:map_network_error:132-138 timeout→超时提示/其他→网络错误;map_status_error:141-151 401/403→鉴权失败/404→端点不存在/5xx→厂商异常;全含 provider_type+url 上下文。
- **⑥fetch_and_probe probe 兜底 PASS**:fetch_and_probe:52-59 探测为纯 CPU map collect,单模型探测失败不中断整体(probe 本身有兜底)。
- **⑦UX-01 canRetry Fatal 分类 PASS**:AiChat.vue:1994-1999 auth/provider_config=Fatal 隐藏重试;notRetryable 提示 L413;v-if L415 守卫;边界说明正确(404/参数错仍可重试,待后端 retryable 字段精确化)。
- **⑧UX-03 agentRound watch 触发时机 + CSS 不动决策 PASS**:watch agentRound:1738-1744 cur<=prev 早退,collapseAllToolLists(buildActiveToolIds(...));ToolCardList.vue:353-357 flex gap 原理说明明确,本轮不重构 JS 钩子决策合理。
- **⑨i18n 转义 PASS**:ai.ts/aiChat.ts zh-CN/en 8 key 全纯文本,无 @/|/裸 {} 特殊字符;notRetryable/queueEdit/queueSave/queueCancel/queueSendNow 双语对称。
- **⚪ CR-45-1(low,`-search` 子串匹配可能误伤)**:is_non_chat_model:218 `id.contains("-search")` 可能误伤含 `-search` 后缀的合法 chat 模型名(如厂商未来发布 `gpt-next-search-assistant` 模型实际支持 chat);当前单测覆盖 `text-davinci-search-001` 旧 GPT 搜索变种合法剔除,但 `contains` 子串匹配设计粒度粗。已知影响面小(主流厂商暂无含 `-search` 的 chat 模型),非阻断;后续可按需收紧为正则 `r#"-(search|search-[a-z]+)$"#` 或加入白名单例外。
- **⚪ CR-45-2(low,fetch_and_probe 无分页)**:fetch_anthropic_compat:92-119 调 `build_models_url``/v1/models`,Anthropic 响应含 `has_more` 字段但当前 serde 反序列化 ModelsList:248 未取 has_more(serde 仅取 id);超大列表>500 可能截断。当前阶段3 聚焦拉取主流模型,截断影响可控(头部 500 模型足够覆盖);非阻断,留阶段5 IPC 实测后按需补分页。
- **待修项回流 todo**: **无**(2 low 非阻断:子串匹配可收紧/分页可后续补)。
---
### CR-260616-46 波2 F-01阶段4路由器 + UX-05/06/07队列三件套 — ✅ 已审(PASS)
- **复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc,87s)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai router::` **15 passed**(13 原+2 新)/ `npx vue-tsc --noEmit` EXIT 0。
- **①select 7 步过滤链 PASS**:router.rs:57-66 严格对齐设计 §6.1 L470-478(enabled→模态全子集 L60→needs_tool_use 含 ToolUse L61→intelligence>=min L62→max_cost<= L63→context_window>= L64→max_by_key L65)。单测 full_dimensions_match_picks_best:L310-360 5 候选参差验证全链。
- **②Reverse(cost_tier) 取反正确性 PASS**:router.rs:11 `use std::cmp::Reverse`+L65 `max_by_key(|m| (m.weight, Reverse(m.cost_tier)))`.CostTier Ord Free<Low<Medium<High,Reverse 让小值变"大"选便宜(设计原文 -(as i32) 等价但更 idiomatic)。单测 same_weight_picks_cheaper_cost_tier:L288-307 验证 Free 胜 High。
- **③select_model_id helper PASS**:router.rs:79-81 纯函数 `select_model_id(req,pool)->Option<String>` 调 select 映射 model_id。单测 select_model_id_*:L117-128 池空/命中全过。
- **④关联函数 vs &self 方法偏离 PASS**:router.rs:9 注释注明「对齐任务规格,非设计文档的 &self 方法」。关联函数 `ModelRouter::select(&req,&pool)`(L57)适配阶段5 调用点 `ModelRouter::select(&req,&pool)`,零行为差异(无状态单元结构,无需 self)。
- **⑤pub use re-export PASS**:router.rs:16 `pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig}`。阶段5 调用点经 `df_ai::router::{...}` 直接 import 维度枚举构造 TaskRequirements,零跨 crate 路径冗长。
- **⑥UX-05 决策 a 落地 PASS**:useAiSend.ts:397-404 stopChat 删 `state.queue=[]` 决策 a(逐条续发)。后端 agentic.rs:209 注释「保证前端收事件时后端已可接下一条,队列续发不被拒绝」+drainQueue(useAiSend.ts:228-232)由 AiCompleted 触发链式续发。
- **⑦UX-06 editQueued PASS**:useAiSend.ts:370-375 边界(index 越界/空白 no-op)+trim+直接赋值 text。AiChat.vue:1567 editingQueueIdx/1569 editingQueueText ref+L519-531 inline input 三路保存一致(回车/blur/@mousedown.prevent 防 blur)。
- **⑧UX-07 sendQueuedNow 顺序 PASS**:useAiSend.ts:387-392 splice 先→stopChat→sendMessage 顺序关键(splice 前置避重复发,stopChat UX-05 后已不清队列故 splice 必须前置)。复用 UX-05 stop 链路(agentic.rs:209 AiCompleted 就绪核验)。
- **⑨i18n 双语 PASS**:queueEdit/Save/Cancel/SendNow 4 key 纯文本无特殊字符,zh-CN/en aiChat.ts:L68-71 对称。
- **⚪ low 清单**:①router.rs:77 select_model_id 注释「池空/无匹配→None→兜底 default_model」。阶段5 调用点 unwrap_or(default_model) 确认行为不变,当前测试见 CR-48 调用点接入验证。
- **待修项回流 todo**: **无**(9 重点全 PASS,1 low 注释清晰非阻断)。
---
### CR-260616-47 波3 F-15上下文基础(is_active白名单+push token) + F-01模型拉取IPC+Settings UI — ✅ 已审(PASS)
- **复审结论(2026-06-17·审查 agent 亲跑 cargo+test+vue-tsc,62s)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai-core is_active` **1 passed** / `cargo test -p df-ai context::` **14 passed**(零回归)/ `npx vue-tsc --noEmit` EXIT 0。
- **①is_active 白名单零行为变化 PASS**:provider.rs:85-86 `matches!(self.status.as_deref(), None | Some("active"))` 与旧 `!matches!(self.status.as_deref(), Some("truncated"))` 等价(当前仅 None/active/truncated 三态)。**核验 truncated 时序**:context.rs:484-496 truncate_after_user_message 在 push 之后标记(:496 `t.message.status = Some("truncated")`),push 时刻 is_active 恒 true,history_tokens 累加不变,零行为变化证成。
- **②push token 修正 PASS**:context.rs:179-185 `if message.is_active() { self.history_tokens += tokens }`。!active 消息仍 push 到 self.messages 全量保留(:186 `self.messages.push(...)`),仅不计 token。修复 restore_from_messages 后 token 虚高致 build_for_request 误判超预算。
- **③restore_from_messages 路径 PASS**:context.rs:420-425 遍历调 push,token 同步仅 active。单测 push_token_only_active:860-910 验证直接 push+restore 双路径,active/archived/compressed 三条 restore 后 token 只含 active 一条(:902-910 断言通过)。
- **④ai_fetch_models FR-S1 闭环 PASS**:commands.rs:872 `df_storage::secret::resolve_provider_secret(&provider)` 内存解析,不进日志/返回值。ModelConfig(df-ai-core/model.rs:87-125)无 api_key 字段,:902 返回值 `Vec<ModelConfig>` 天然不含密钥。
- **⑤normalize_provider_type 归一 PASS**:commands.rs:841-846 anthropic→anthropic_compat 归一(:873 调用)。对齐 model_fetch.rs:40-42 fetch_and_probe 协议分派,双命名收敛。
- **⑥fetch_and_probe + update_full 写回 PASS**:commands.rs:875 `fetch_and_probe` 调用 + :883-887 `update_full` 写回 model_configs + updated_at。
- **⑦ai_probe_model 纯 CPU probe PASS**:commands.rs:897-902 调 `df_ai::model_probe::probe(&model_id)`,无网络。
- **⑧types.ts 4 维度枚举 snake_case 对齐 PASS**:types.ts:200-206 Modality/Capability/CostTier/IntelligenceTier 字面量(df-ai-core/model.rs:20-72 serde `rename_all = "snake_case"` 严格对齐)。
- **⑨Settings 拉取 UI PASS**:Settings.vue:95 拉取按钮(:96 disabled 非新建态提示先保存)+:102-103 模型列表展示(4 维度标签+enabled toggle+weight input)+:519-536 fetchModels 函数错误分支 toast(超时/鉴权/404/厂商异常)。
- **⑩i18n 双语 PASS**:zh-CN/en settings.ts:33-37 testConnection/fetchingModels/fetchHintSaveFirst/labelWeight/modelListTitle 对称,无特殊字符。
- **⚪ CR-47-1(low,Settings enabled/weight 本地态非持久化)**:Settings.vue:525 `providerForm.models = models.map(...)` 仅本地会话态,未存回 model_configs。文案 "下次「测试连接」会以最新探测结果覆盖"(:516-518 注释)明示,阶段6 后续持久化另见 issues。非阻断(本批目标拉取展示 UI,本地编辑态可接受)。
- **⚪ CR-47-2(low,ai_probe_model UI 未接)**:无单模型探测按钮,留阶段6 AiChat 模型下拉。非阻断(probe 纯 CPU,按需补 UI)。
- **待修项回流 todo**: **无**(10 重点全 PASS,2 low UI 增强/持久化非阻断)。
---
### CR-260616-48 波4 F-01阶段5调用点接入(select_model_id helper+6点) + F-260617-01 patch_file三模式编辑 — ✅ 已审(PASS)
- **复审结论(2026-06-17·CR-48 agent 亲跑 cargo+test+vue-tsc,342s)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai router` **15 passed**(2 新增:select_model_id_empty_pool_returns_none/select_model_id_hit_returns_model_id)/ `cargo test -p df-nodes --lib` **79 passed; 1 ignored**/ `cargo test -p df-ideas adversarial` **12 passed**(含 a12 新增 llm_mock_fenced_not_at_start)/ `cargo test -p devflow tool_registry` **24 passed**(16 三模式单测)/ `npx vue-tsc --noEmit` EXIT 0。
- **①select_model_id helper 纯函数正确性 PASS**:router.rs:79-81 纯函数调用 ModelRouter::select + map model_id,单测:117-127 覆盖池空 None/命中 model_id 返回字符串,编译期证类型匹配。
- **②pub use re-export 可见性 PASS**:router.rs:16 `pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig}`;6 调用点全经 `df_ai::router::{...}` 全限定导入(agentic.rs:19/project.rs:11/title.rs:11/knowledge_inject.rs:12/ai_node.rs:17/adversarial.rs:165),编译期证零断裂。
- **③6 调用点兜底行为不变 PASS**:agentic.rs:175-176 `select_model_id(...).unwrap_or_else(|| provider_config.default_model.clone())` 池空/无匹配→None→兜底 default_model;title.rs:86-87/project.rs:539-540/knowledge_inject.rs:350-351/ai_node.rs:214/adversarial.rs:165 同模式;池空时 resolved_model==default_model 零差异。
- **④agentic resolved_model 审计字段统一 PASS**:agentic.rs:288/410/414/419/465/469 原用 default_model 处统一改 resolved_model,语义更正确(记实际服务模型);池空时 resolved_model==default_model 无行为变更。
- **⑤knowledge_inject 嵌入降级 needs_tool_use=false PASS**:knowledge_inject.rs:58-65 TaskRequirements(needs_tool_use=false,min_intelligence=Lite),宽松过滤允许嵌入模型入选;TaskRequirements 无 capabilities 字段设计偏离,embedding provider 池通常即嵌入模型实践中命中,可接受。
- **⑥df-ideas 新增 df-ai 重依赖 PASS**:df-ideas/Cargo.toml:12 df-ai 依赖引入;注释:9-11 说明 router 模块仅依赖 df-ai-core::model,编译期 tree-shaking 零 HTTP 代码链入(仅引用符号),实测 cargo check EXIT 0 证无链接错误;后续若严格轻量可将 router 下沉 df-ai-core 非阻断。
- **⑦df-nodes ResolvedProvider.model_pool 字段 PASS**:ai_node.rs:54 model_pool 字段+ :205 record 路径填 record.model_configs.clone + :143 老明文路径填空池;config.model 显式优先(:197-203 有则用 model_id 否则路由),单测覆盖。
- **⑧patch_file 三模式互斥运行时校验 PASS**:tool_registry.rs:932-940 has_old_text/has_replace_lines/has_anchor 计数,0=缺定位 bail/:937 >1=冲突 bail/:939,两边界完备;schema 层 object_schema 无法静态互斥,运行时兜底合理。
- **⑨patch_file expected_hash mtime+size 非 sha256 PASS**:tool_registry.rs:971-981 `format!("{}_{}", modified.unwrap_or(0), file_meta.len())` 指纹(mtime+size),三模式通用,语义与 old_text 模式一致(read_file 返回 size 非 hash,注释:141 与 read_file 行计数一致),设计正确。
- **⑩apply_line_range trailing newline 语义 PASS**:tool_registry.rs:133-152 单测:1722-1729 尾换行保留(`\n` 结尾文件替换后仍以 `\n` 结尾),splice 循环 lines() 后单独处理 trailing_newline,边界正确。
- **⑪resolve_anchor 子串行内命中 PASS**:tool_registry.rs:182-206 子串大小写敏感匹配+start 后第一个含 end 行;单测:1783-1789 行内命中 / 1794-1798 start==end 同行 / 1803-1808 first-end-after-start,覆盖完备。
- **⑫16 单测覆盖 PASS**:tool_registry.rs:1701-1853 单测 apply_line_range 8 + resolve_anchor_to_lines 8 + e2e 1,亲跑 cargo test 24 passed 全过。
- **⚪ CR-48-1(low,agentic 主对话含图 Vision 检测未实现)**:agentic.rs:169 modalities 仅 `[Modality::Text]`,ChatMessage 纯文本无图像 Part,多模态类型接入后需检测 Part/Image 追加 Vision,当前非阻断(设计文档已标注:166-167)。
- **⚪ CR-48-2(low,build_provider/诊断日志仍用 default_model)**:provider trait 不暴露改 default_model,CompletionRequest.model 走路由 provider impl 适配(OpenAICompatProvider::convert_request:295-296 req.model.is_empty() 回退 default_model),诊断日志仍用 default_model 字段名,非阻断。
- **待修项回流 todo**: **无**(2 low 非阻断:Vision 检测待多模态接入/诊断日志命名,路由核心功能全 PASS)。
### CR-260616-49 波5 F-01阶段6 AiChat模型下拉+override穿透(4c84fbc) / F-15阶段1剩余compress基础(63bff8b) — ✅ 已审(PASS)
- **复审结论(2026-06-17·CR-49 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **验证**: `cargo check --workspace` EXIT 0(6 warning 全 pre-existing dead_code,PendingApproval/releases/node_executions)/ `npx vue-tsc --noEmit` EXIT 0/ `cargo test -p df-ai` **107 passed**(含 context 9 新方法 + 8 新单测)/ `cargo test -p devflow --lib compress` **3 passed**
- **①F-01 override 兜底零行为变化 PASS**:agentic.rs:181-188 `match model_override.as_deref()` — Some(id) 且 !id.is_empty() 且 `provider_config.model_configs.iter().any(|m| m.model_id == id)` → 用 id;`_ => resolved_model`(落回路由)。override=None(自动模式默认)完全等价改动前。非空但不在池→落回路由兜底正确,防止 override 导致无模型。
- **②F-01 清零路径完整 PASS**:create(commands.rs:1147 `session.model_override = None`)+ switch(commands.rs:1225)两入口清零,前端 watch activeConversationId 同步清(AiChat.vue:1071)。防跨对话污染,零残留。
- **③F-01 续跑透传 PASS**:try_continue_agent_loop(agentic.rs:699-702)读 session.model_override.clone() 透传,审批续跑/达 max 续跑保持同一主对话的 override 一致,语义正确。
- **④F-01 空池降级 PASS**:AiChat.vue:237 `v-if="enabledModels.length"` 隐藏下拉+ :231 toggle :disabled + :1323 setSpecifyMode 早返。空 provider 不 crash。
- **⑤F-01 i18n 特殊字符 PASS**:aiChat.ts 新 6 key(autoMode/specifyMode/autoModeHint/specifyModeHint/selectModel/noModels)纯文本,无 @/|/裸{} 特殊字符。
- **⑥F-15 compress 幂等 PASS**:context.rs:629-640 compress_old_messages 只处理 is_active,saturating_sub 扣 token,已 compressed 跳过;返回 newly_compressed 仅本次新标记。DB 原始消息保留,is_active 白名单隔离。单测 push_token_only_active:954 覆盖直接 push+restore 双路径。
- **⑦F-15 compress_via_llm 兜底 PASS**:compress.rs:66-67 select_model_id 池空/无匹配→unwrap_or_else 兜底 default_model;:89 map_err + :92 空摘要 Err。阶段2 IPC 接调用点后降级。
- **⑧F-15 纯函数优先 PASS**:compress_prompt(prompt.rs:148 无 IO)/ build_eviction_units/has_compressible_messages/compress_old_messages 无 IO;compress_via_llm 封装 IO(双层 Semaphore 限流复用 title 模式)。单测 3 passed。
- **⑨F-15 零行为变化 PASS**:push/restore_from_messages/build_for_request/sanitize_messages 未改,仅新增方法 + is_compressing 字段(new/clear 初值 false)。
- **⚪ CR-49-1(low,前端 ref 单例设计说明)**:modelOverride 模块级 ref(useAiSend.ts:53)非 per-conversation,靠 watch 清理对齐后端 switch 清 session.model_override。当前单实例 AiChat 可接受,多窗口架构未来若需独立模型选择需改 per-conversation ref。非阻断(设计文档已标注)。
- **待修项回流 todo**: **无**(9 重点全 PASS,1 low 设计说明非阻断)。
### CR-260616-50 波6 F-15阶段2 手动上下文管理(2 IPC+3事件+前端按钮+status渲染)(4194842) — ✅ 已审(🔴 RED 已修 PASS)
- **复审结论(2026-06-17·波7 ww68uamzu agent CR-50)**: ⚠ **ISSUES → 🔴 RED 已修(主代)** — 后端 IPC/事件/保护区/失败路径全 PASS,前端事件分发 RED(审查抓到真 bug,主代即时修)
- **验证**: `cargo check --workspace` EXIT 0(6 warning 全 pre-existing dead_code)/ `npx vue-tsc --noEmit` EXIT 0(零输出)/ `cargo test -p devflow --lib` **79 passed**(无回归)。
- **① clear_context 不删 DB PASS**:commands.rs:430-433 经 messages_mut() 标 status="archived_segment"(is_active 自动 false,sanitize 隔离),落库走 save_conversation 全量 upsert(:439),非 clear_messages 删库。
- **② compress LLM 失败不阻塞 PASS**:commands.rs:543-554 先取 active 克隆(读不改 status)→ compress_via_llm 失败则 set_compressing(false)+ emit AiError(message 不含 api_key)+ 返 Err;消息状态不变(未调 compress_old_messages)。成功路径才 compress_old_messages 标 compressed + insert_at(0, summary system)。
- **③ 保护区最近 N 条 active 不动 PASS**:PROTECT_COUNT=6(commands.rs:412/465 两处一致),protect_start = len.saturating_sub(6),clear/compress 只对 [0, protect_start) 操作。
- **④ 空会话/无 active 降级 PASS**:protect_start==0 → emit 对应事件 + Ok(不报错,commands.rs:416-420/485-492);active_msgs 空 → AiCompressed(summary 空,commands.rs:511-515) + Ok。
- **⑤ 3 事件变体 serde — 🔴 RED(审查抓到真 bug)**:mod.rs AiChatEvent 仅 `#[serde(tag="type")]``rename_all="snake_case"`,serde 默认序列化 PascalCase(useAiEvents.ts 10 case 铁证)。useAiContext.ts:77/80/84/87 switch case 用 snake_case(`'ai_compressing'` 等)→ 永不命中 → isCompressing 永不复位 → 压缩按钮用过一次永久禁用 + 清空后视图不刷新。**主代修复(2026-06-17)**:case 4 处改 PascalCase(AiCompressing/AiCompressed/AiContextCleared/AiError)+ compressContext 成功兜底复位(后端同步 resolve 非异步)+ doc 注释一致性。vue-tsc EXIT 0 + grep 无残留 snake_case + useAiEvents 对照铁证。未改后端(rename_all 会破坏 useAiEvents 既有 10 case)。
- **⑥ useAiContext composable 闭环 PASS(RED 修后)**:isCompressing ref 防重入 + 第二 listener 消费 4 事件 PascalCase 分发 + compressContext 成功兜底复位(后端 IPC 同步等 LLM 完成才 resolve 非异步 spawn,成功即复位防事件丢失)。
- **⑦ status 分组渲染 PASS**:AiChat.vue:2240 messageSegments computed 按 status 分组(archived_segment/compressed 连续合并),:2290 renderItems 扁平化供 v-for;折叠段点击 toggleSegment 展开,正常消息零改动。
- **⑧ i18n 13 key ×2 无特殊字符 PASS**:aiChat.ts 新 key(clearContext/compressContext/clearContextConfirm/contextArchived/compressed/compressing/clearSuccess/compressSuccess/compressError/clearError/expand/collapse/compressedSummaryLabel)仅合法 {n}/{msg} 插值,无 @/|/裸 {}。
- **⑨ useAiConversations status 透传 PASS**:useAiConversations.ts:86-90 switchConversation mapper 透传 status 字段(archived_segment/compressed 落 AiMessage 供渲染),types.ts 未含此字段经 as any 读写闭环。
- **⑩ 已知取舍(history_tokens 口径) PASS**:commands.rs:423-429 注释明确标注口径——clear_context 改 status 后 history_tokens 不立即扣减(私有字段无 setter),后果短暂偏高触发更早裁剪,属保守方向不劣化安全性。
- **待修项回流 todo**: **无**(🔴 RED 已即时修,主代核验 case PascalCase + vue-tsc EXIT 0 + useAiEvents 10 case 铁证对照)。
- **范围**: 2 agent 并行 workflow w6o61cbqn,文件域隔离(后端独占 commands.rs/mod.rs/lib.rs;前端独占 api/ai.ts/useAiContext.ts(新)/useAiConversations.ts/stores/ai.ts/AiChat.vue/i18n×2)。零重叠。
- **commit**: 4194842(单 commit,功能完整:后端 IPC 无前端无意义,配套提交)。
- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(6 warning 全 pre-existing dead_code,PendingApproval/releases/node_executions)/ npx vue-tsc --noEmit EXIT 0(零输出)/ cargo test -p devflow --lib **79 passed**(无回归)/ grep 核验形态(lib.rs:122-123 注册 2 IPC + mod.rs:145/147/149 三事件变体 + commands.rs:397/457 两 IPC fn + 6 处 emit + 前端 useAiContext composable + AiChat.vue messageSegments 分组渲染)。
- **审查 agent 待复审重点**:
- ①**clear_context 不删 DB**:commands.rs:397 经 messages_mut() 标 status="archived_segment"(is_active 自动 false,sanitize step0 隔离),落库走 save_conversation(全量 upsert 非 clear_messages 删库)。
- ②**compress LLM 失败不阻塞不 panic**:commands.rs:457 先取 active 克隆(读不改 status)→ compress_via_llm;LLM 失败 set_compressing(false)+ emit AiError(message 不含 api_key)+ 返 Err;消息状态不变(未调 compress_old_messages)。成功路径才 compress_old_messages 标 compressed + insert_at(0, summary system)。
- ③**保护区最近 N 条 active 不动**:PROTECT_COUNT=6(commands.rs:413/465),protect_start = len.saturating_sub(6),clear/compress 只对 [0, protect_start) 操作。
- ④**空会话/无 active 降级**:protect_start==0 → emit 对应事件 + Ok(())不报错;active_msgs 空 → AiCompressed(summary 空) + Ok。
- ⑤**3 事件变体 serde**:mod.rs:145/147/149 AiContextCleared/AiCompressing/AiCompressed{summary},继承枚举级 #[serde(tag="type")] rename_all snake_case → 前端 type:"ai_context_cleared"/"ai_compressing"/"ai_compressed"。
- ⑥**前端 useAiContext composable 闭环**:isCompressing ref 防重入 + 第二 listener 消费 ai_compressing/compressed/context_cleared/error 生命周期;compressContext IPC 受理仅表请求受理,真正完成由 ai_compressed 事件复位 isCompressing(异步闭环,非 IPC 同步等)。
- ⑦**status 分组渲染**:AiChat.vue messageSegments computed 按 status 分组(archived_segment/compressed 连续合并折叠段),renderItems 扁平化供 v-for;折叠段点击 toggleSegment 展开,正常消息走 v-else 原渲染零改动。
- ⑧**i18n 13 key ×2 禁字符**:aiChat.ts 新 key(clearContext/compressContext/clearContextConfirm/contextArchived/compressed/compressing/clearSuccess/compressSuccess/compressError/clearError/expand/collapse/compressedSummaryLabel)message 值无字面 `@`/`/`/裸`{}`(仅合法 `{n}`/`{msg}` 插值)。注意 archived 与侧栏冲突 → 重命名 contextArchived(vue-tsc TS1117 已修)。
- ⑨**useAiConversations status 透传**:switchConversation mapper 透传 status 字段(archived_segment/compressed 落 AiMessage 供渲染折叠),types.ts 未含此字段经 cast 读写闭环。
- **⚠️ 已知设计取舍(backend 自报,记审查项)**:clear_context 经 messages_mut() 改 status 后,ContextManager.history_tokens(私有字段无 setter)不立即扣减 archived 段 token(与 compress_old_messages 路径不同,compress 内部扣 token)。后果:history_tokens 短暂偏高,build_for_request 超预算裁剪可能比实际更早触发,属保守方向(不超发)不劣化安全性。代码注释已标注口径。
### CR-260616-51 波8 F-15阶段3 agentic loop自动压缩(0202b51) + AE-2025-04会话级授权(6ec191e) — ✅ 已审(PASS-WATCH·🟡1回波11修)
- **复审结论(2026-06-17·波10 wsm0tk95c agent CR-51)**: ⚠ **PASS-WATCH** — 🔴0 🟡1 ⚪2。阶段3(loop语义/触发/防重入成对/延迟mutate/降级/emit路由)+ AE-04(TrustKey/session_trust/audit查trust/ai_approve写trust/toast桥接/FR-S1)逻辑全 PASS,devflow 79 + df-ai 107 passed,vue-tsc EXIT 0。**🟡 yellow 回流波11修**:audit.rs:593-596 trust-hit 放行分支 `.await tools_arc.execute``&mut session`(注释 L593 声称锁外但代码持锁,与 Low risk spawn 模式不一致),run_command 慢命令阻塞该会话所有触 state.ai_session 的 IPC(30+)。修法:对齐 Low risk spawn+clone(trust_hits Vec 收集→循环外 join_all spawn execute 锁外→串行回填持锁)+ 修正注释。⚪2 非阻断(摘要 insert_at 二次压缩边缘/canonicalize 失败回退边缘)。
- **范围**: 2 agent 并行 workflow wigdmq5s4,文件域零重叠(阶段3 独占 agentic.rs;AE-04 独占 mod.rs/audit.rs/commands.rs/tool_registry.rs/AiChat.vue/useAiEvents.ts/types.ts/i18n×2)。
- **commit**: 0202b51(阶段3)+ 6ec191e(AE-04)。分 2 commit(语义清,功能不同)。
- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(6 warning 全 pre-existing dead_code)/ npx vue-tsc --noEmit EXIT 0(零输出)/ cargo test -p devflow --lib **79 passed**(无回归)/ grep 核验形态(阶段3 loop 顶部压缩分支 + set_compressing 成对 + AE-04 TrustKey/session_trust/audit 查 trust/ai_approve 写 trust)。
- **审查 agent 待复审重点**:
- ①**阶段3 loop 语义不变**:agentic.rs loop 顶部加压缩检测分支(L259-374),主 loop 迭代/stream/重试/tool_calls/converge 零改动。压缩是 build_for_request 前预处理。
- ②**阶段3 触发条件**:`budget*6/10 < history_tokens`(0.6 阈值整数比避浮点)+ protect_start>0 + has_compressible_messages(protect_start)。
- ③**阶段3 防重入 + 成对**:prev_compressing 守卫(L280)+ set_compressing(true)(L303)↔ 三出口 false(L340 成功/L355 noop/L361 Err)。成对覆盖所有分支。
- ④**阶段3 延迟 mutate(成功才改)**:先取 active 克隆 → LLM 出摘要 → 成功才 compress_old_messages(标 compressed+扣 token)+ insert_at(摘要)。失败零副作用(消息状态/token 完全不变)。理由:history_tokens 私有无 setter,失败回滚 token 不精确;延迟 mutate 语义最干净。
- ⑤**阶段3 降级**:LLM 失败/空摘要 → set_compressing(false)+ emit AiError(message 不含 api_key)+ 继续走原 build_for_request 裁剪(不阻塞 loop)。
- ⑥**阶段3 emit conversation_id 路由**:AiCompressing(L298)/AiCompressed(L348)/AiError(L367)三处均 Some(conv_id)。
- ⑦**AE-04 TrustKey 设计**:mod.rs:201 enum Write{dir}/Execute{dir}(目录级,derive Hash/Eq)+ trust_key_for(:215 write_file→Write 父目录/run_command→Execute working_dir)+ 首批仅 write_file/run_command 命中(其余 None 走原审批)。
- ⑧**AE-04 session_trust 内存态**:HashSet<TrustKey>(mod.rs:346),new 初值空(:363),换会话(create/switch)clear(commands.rs:1161/1241)重审,不落库。
- ⑨**AE-04 audit 查 trust**:audit.rs:568 process_tool_calls 审批前(Med/High 进 pending 前)查 session_trust.contains 命中 → 直接执行(非复用缓存,与 F-05 去重不同)+ emit AiToolAutoApproved(:587)+ Completed + audit decided_by=auto_trust(:613)留痕 + continue 跳过 pending + 二次确认。
- ⑩**AE-04 ai_approve 写 trust**:commands.rs:294-295 用户批准时 trust_key_for 命中 → session_trust.insert(下次同类自动放行)。
- ⑪**AE-04 前端 toast 桥接**:AiToolAutoApproved 后端 emit → useAiEvents case(types.ts union)→ emit ai-tool-auto-approved-toast 事件总线 → AiChat.vue listen 弹 info toast(🔓 自动放行),非审批气泡避免干扰。
- ⑫**AE-04 FR-S1**:TrustKey 只含 tool+dir,不含 api_key/文件内容/命令串(mod.rs:189 注释)。
- **待修项回流 todo**: **无**(核心 grep+test+构建全 PASS;阶段3 延迟 mutate 口径 + AE-04 目录级粒度为设计取舍非 bug)。
### CR-260616-52 波9 B-260617-01 run_workflow审批执行(方案a2后端分支)(e23b316) — ✅ 已审(PASS)
- **复审结论(2026-06-17·波10 wsm0tk95c agent CR-52)**: ✅ **PASS** — 🔴0 🟡0 ⚪3。a2 实施正确:run_workflow_inner 抽取语义零变化(命令层瘦转发)/ ai_approve 分支(commands.rs:317)仅隔离 run_workflow 其余不变 / execute_run_workflow_for_tool 回填 Ok json(execution_id)让 LLM 收成功 / AE-04 trust_key_for(:294)在分支(:317)前且 run_workflow 返 None 不写 trust / audit trust 查询 + create/switch clear 未动 / High 去重缓存断重试双层 / handler 防御文案正常不可达 / FR-S1 tool_result 仅 task_id+target_status+execution_id。devflow 79 passed。⚪3 非阻断:①workflow.rs:124 triggered_by 注释声称 ai 实际 manual(已知待办)②mod.rs:465 空 config 既有行为(对齐前端)③tool_registry.rs:547 _db 死代码冗余。
- **范围**: 单 agent workflow wf8r5vlno,走查+实施。改 workflow.rs(run_workflow_inner 抽取)/ mod.rs(execute_run_workflow_for_tool)/ commands.rs(ai_approve run_workflow 分支)/ tool_registry.rs(handler 注释+防御文案)。
- **commit**: e23b316。
- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(6 warning 全 pre-existing)/ cargo test -p devflow --lib **79 passed**(无回归)/ grep 核验形态(workflow.rs:87 run_workflow_inner + mod.rs:446 execute_run_workflow_for_tool + commands.rs:317 ai_approve 分支 + trust_key_for:294 在分支前 AE-04 不破坏)。
- **方案选型**:agent 选 a2(后端分支)而非 a1(前端拦截)。理由:ai_approve 已持 app+state,后端直接调 run_workflow_inner 最干净,无需新 IPC/前端分支/工作流知识泄漏。符合用户推荐 a 大方向(绕开 ai_tools.execute 必 Err)。
- **审查 agent 待复审重点**:
- ①**run_workflow_inner 抽取语义零变化**:workflow.rs:87 抽取 run_workflow 命令体为共用核心,命令层瘦转发(:79)。核心逻辑(模板选 DAG/落记录/spawn 转发+执行+联动)全部移入,语义不变。
- ②**execute_run_workflow_for_tool**:mod.rs:446 从 args 取 task_id/target_status + 构造空 DagDef+config + name 拼"AI 推进任务" + 调 run_workflow_inner + 回填 {task_id,target_status,execution_id,status:"running"} 让 LLM 收成功。
- ③**ai_approve run_workflow 分支**:commands.rs:317 `if approval.tool_name == "run_workflow"` → execute_run_workflow_for_tool,否则原 ai_tools.execute。后续 audit_finalize/emit_data_changed/replace_tool_result/emit/save/try_continue 共用零变化。
- ④**AE-04 trust 不破坏**:trust_key_for 写 trust(:294)**在 run_workflow 分支(:317)前**;audit trust_hit 查询(audit.rs:568)未动;create/switch clear(:1177/1257)未动。
- ⑤**重试循环根治**:a2 真实执行→成功 tool_result(非 Err)+ 既有 High 去重缓存(find_cached_high_risk_result audit.rs:406 失败 tool_result 复用)双层保障。不新增 no_retry 标志(无字段+无消费方+crate 改动大,去重缓存已等效)。
- ⑥**handler 防御文案**:tool_registry.rs:556 run_workflow handler Err 改防御兜底(正常流程不可达,a2 分支接管)+"勿盲目重试"明示(对齐 run_command 超时标注模式)。
- ⑦**FR-S1**:run_workflow 无 api_key,tool_result 仅 execution_id+task_id+target_status。
- **待修项回流 todo**: **无**(a2 实施正确,grep+test+构建全 PASS;no_retry 不增为设计取舍,去重缓存等效)。
### CR-260616-53 波11 CR-51 yellow修(audit trust-hit持锁重构) + UX-01工具失败视觉 + UX-03自动收起增强(332a6bb+31ea151) — ✅ 已审(PASS·⚪2)
- **复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc+test+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **验证**: `cargo check --workspace` EXIT 0(7 warning 全 pre-existing dead_code)/ `npx vue-tsc --noEmit` EXIT 0/ `cargo test -p devflow --lib` **88 passed**(无回归)。
- **①CR-51 yellow 并行化(非"移锁外") PASS**:修复前后 session 锁均由调用方(agentic.rs:839 `session_arc.lock().await`)持有至 process_tool_calls 返回,锁持有时长不变。**真正改进是并行化**:修复前 trust-hit 分支内 inline `.await execute` 串行执行每个工具(阻塞期间锁被持有,run_command 慢命令阻塞同会话所有触 state.ai_session 的 IPC);修复后收集 trust_hits Vec→join_all 并行执行(锁仍被持有,但多工具并行减少总阻塞时间)。audit.rs:664-713 实现(trust_hits 收集 L568+join_all 闭包 L671-701+串行回填 L704-712)正确,注释"锁外"应理解为"闭包内不访问 session"而非"锁已释放"。
- **②语义不变 9 条全 PASS**:真执行(audit.rs:676 tools.execute)+emit toast(L599 AiToolAutoApproved)+emit completed(L680)+emit_data_changed(L687)+audit auto_trust(L711 decided_by=Some("auto_trust"))+Med-High 非 trust 路径零改动(L630-660 pending 分支)+Low risk 零改动(L718-764 独立并行分支)+pending_count 不变(L630 仅 trust-hit 跳过,pending 仍累加)+错误信息格式不变(L680/690 AiToolCallCompleted 结构一致)。
- **③UX-01 isToolFailure 判定可靠 PASS**:run_command 路径(ToolCard.vue:213-217 parseResult 命中合法 JSON 看 exit_code≠0,可靠)+通用工具路径(ToolCard.vue:220-224 parseResult 返回 null 后正则 `/执行失败|failed|error[:\s]/i` 启发式匹配,对齐后端 AR-6 err_msg 格式 `format!("工具 {} 执行失败: {}", draft.name, e)` audit.rs:744)。正则可能误判含 "error"/"failed" 字样的成功结果(如 "no errors found"、"error handling passed"),但这是启发式检测已知取舍,非阻断(误报概率低,最坏多一次视觉提示)。
- **④UX-01 视觉完整 PASS**:--failed 红框(ToolCard.vue:727-730 border-color+box-shadow)+banner(L119)+红底结果框(L745-751 background+border)+状态点(L20 failed 分类)+副标题(L17 failed 标签)。CSS 三类(.ai-tool-card--failed/.ai-tool-status-dot--failed/.ai-tool-result--failed)完整。
- **⑤UX-03 streaming watch 守卫 PASS**:AiChat.vue:1807 `generatingConvId===activeConversationId` 防后台生成误触+防恢复会话 onMounted 时 streaming 初值 true 的误触(仅当前视图正在生成时触发收起)。守卫正确。
- **⑥UX-03 wasNearBottom 边沿检测 PASS**:AiChat.vue:1749 初值 true+1760 `if (near && !wasNearBottom)` 转换瞬间触发一次+1763 `wasNearBottom = near` 更新。边沿检测防每 scroll tick 收起,正确。
- **⑦UX-03 userExpandedCards 记忆态 PASS**:ToolCardList.vue:71 定义+197 展开时 add+193 收起时 delete+180 isCardExpanded 优先读+221 collapseInactive 不清除。记忆态防"刚在看被收起",正确。
- **⑧join_all 保序 PASS**:trust_hits 收集按 tc_list 原始 index 顺序(audit.rs:533 sort_unstable_by_key+540 retain 后顺序不变)→join_all 保持输入顺序→串行回填按结果顺序。LLM 按 tool_call_id 匹配 tool_result,不依赖到达顺序,保序非必需但一致性更优。
- **⚪ CR-53-1(low,注释"锁外"有误导性)**:audit.rs:664 注释"execute + 即时 emit 在闭包内(不持 session 锁)"易误解为"锁已释放",实则是"闭包内不访问 session"。session 锁在整个 process_tool_calls 期间由调用方持有(agentic.rs:839 `session_arc.lock().await` 至 L840 `process_tool_calls(...).await` 返回),修复前后锁持有时长不变。改进是**并行化**非"移锁外"。
- **⚪ CR-53-2(low,UX-01 通用工具文本匹配启发式误报风险)**:ToolCard.vue:223 正则 `/执行失败|failed|error[:\s]/i` 可能误判含这些字样的成功结果(如 "error handling passed"、"no errors found")。但这是启发式检测已知取舍,后端 err_msg 格式固定(AR-6 `format!("工具 {} 执行失败: {}", draft.name, e)`),误报概率低,最坏多一次视觉提示不阻断执行,非阻断性问题。
- **待修项回流 todo**: **无**(2 low 全非阻断:注释误导性可后续澄清/启发式误报风险可接受)。
### CR-260616-54 波12 F-04多Provider负载均衡池(数据层+选择器+并发原语) + CR-52白项清理(79b6a43) — ✅ 已审(PASS)
- **复审结论(2026-06-17·CR-54 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: `cargo check --workspace` EXIT 0(7 warning 全 pre-existing dead_code)/ `cargo test -p devflow --lib` **88 passed**(79 基线+9 provider_pool 新增全过)/ `cargo test -p df-storage` **33 passed**/ `npx vue-tsc --noEmit` EXIT 0。
- **①provider_pool select 3键排序完备 PASS**:provider_pool.rs:57-91 过滤 `enabled && weight>0`(:64) + 稳定 3 键(:71-88 模型亲和 true 排前 / weight 降序 / is_default 兜底)。9 单测全覆盖(empty_pool_returns_singleton/single/disabled/zero_weight/model_affinity/model_id_none/weight_tie/is_default/full_scenario),亲跑全过。边界完备:model_id None 全亲和(:73-76 map_or true)/ 0 enabled 空 Vec 调用方兜底(:346-352)/ 1 enabled 单元素首位=唯一(:136-138)。
- **②单 provider 零变化 PASS**:agentic.rs:342-345 ProviderPool::select 取首位,单 provider 池返回 `[p1]`,首位 `p1` 等价原 `provider_config`。老库迁移 V19 migrations.rs:343-356 `DEFAULT 1`/`DEFAULT 50`,crud.rs:1072-1073 unwrap_or(1)/unwrap_or(50),老行启用后等价 F-01 前。空池兜底(:346-352)用 `provider_config.clone()` 作唯一候选,同启动行为。
- **③per_provider permit Option 安全 PASS**:state.rs:112 HashMap 定义 + :158-172 acquire_for_provider 返 `Option<OwnedSemaphorePermit>`,map.get?+acquireOwned(:164-171)。未配 set_provider_caps 时 HashMap 空,返 None,drop(None) 无副作用(:671 `_provider_permit` 绑定作用域自动释放)。本轮 set_provider_caps 无消费方(留 F-04c 前端),单 provider 零变化达成。
- **④agentic 接入完整 PASS**:agentic.rs:342 `ProviderPool::select` 选主候选 + :346-352 split_first 取 primary + candidates + :663-710 `for candidate in &candidate_chain` 循环 + :667 `acquire_for_provider` permit + stream_one_provider(:669-670) 单 provider 流式 + :698 F-04b 日志"切换下一 provider"(基建就位,本轮候选链仅 primary 不触发切换)。单 provider 路径不破坏。
- **⑤triggered_by 透传链完整 PASS**:workflow.rs:93 `run_workflow_inner``triggered_by: &str` 参数 + :133 落 `triggered_by: Some(triggered_by.to_string())` 入库 + :80 命令层传 "manual"(前端人工) + mod.rs:475 ai 路径传 "ai"(execute_run_workflow_for_tool)。grep 核验仅 2 调用点,无遗漏。tool_registry.rs:547-560 删 `let _db = db.clone();` 死代码,handler 仅返 Err 引导走审批路径,正确。
- **⑥V19 迁移幂等 PASS**:migrations.rs:342-360 `PRAGMA column_exists` 探测 enabled/weight + :345 `DEFAULT 1`/ :352 `DEFAULT 50` + V9 建表 SQL(:548-549 同步加列)。老库 ALTER 后取 DEFAULT,新库直接有列。crud.rs:1072-1073 ai_provider_from_row `unwrap_or(1)`/`unwrap_or(50)` 兼容缺失列(幂等测试场景)。insert/update SQL(:1159-1168/:1177-1178) 含 enabled/weight 列。
- **⑦fallback 拆波诚实 PASS**:agentic.rs:330-334 注释明确"F-04b 切换下一 provider"(本 candidate 重试耗尽切下一),:663-710 `for candidate in &candidate_chain` 循环已就位(:698 continue),仅候选链仅 primary(无 fallback 配置)不触发真实切换。基础设施可用,留 F-04b 独立批实际启用多候选切换。
- **⑧4 处构造点补字段 PASS**:commands.rs:986-1001(ai_save_provider 新建默认 enabled=true/weight=50)/ crud.rs:1762-1777(单测)/ secret.rs:209-215(单测)/ ai_node.rs:714-729(单测),4 处全补 `enabled: true, weight: 50`
- **待修项回流 todo**: **无**(7 维度全 PASS,零问题)。
### CR-260616-55 波13 B-21工具卡重复治本(audit tc_list按id去重保证emit Started唯一)(247dc5d) — ✅ PASS
- **范围**: workflow wice28c6y(前端守卫 agent + 后端走查 agent)。①**前端治标守卫**(HEAD 已有,非本轮新加):useAiEvents.ts:205 AiToolCallStarted push 前 `if (!findToolCall(event.id))` 幂等守卫(对齐 startToolSlowTimer:58)。②**后端走查结论**(不改代码):候选A确认——LLM 流式输出两个 tool_use block 不同 content_block index 但相同 id → stream_recv.rs:225 按 index(u32 键非 id)分桶 → 两条同 id 不同 index draft 并存 → audit.rs:538-549 每 draft emit Started → 同 id 两次 → 前端 push 两卡 → Completed 按 id 只 update 首张 → 次张残留 running 0行。候选B排除(provider 每轮新 id)。③**治本兜底(commit 247dc5d)**:audit.rs:533 sort 后加 `seen_ids HashSet + tc_list.retain(draft.id)` 按 id 去重,同 id 保留最小 index 首个,emit Started 唯一。前端守卫双保险。详 docs/02-架构设计/B-260616-21排查方案。
- **维度**: ①去重正确性(同 id 保留最小 index 首个,retain 后 drafts 唯一 id emit) ②不破坏正常多工具(不同 id 不受影响) ③HashSet import(audit.rs:3 加) ④与 F-05(High risk 去重缓存)无冲突(不同维度)。
- **commit**: 247dc5d。
- **主代核查**: cargo check EXIT 0 + 88 passed + grep retain 形态。
- **审查结论**: ✅ PASS。核验通过
- ①去重正确性audit.rs:533 `tc_list.sort_unstable_by_key(|(i, _)| *i)` 先按 index 排序 → :540 `seen_ids.insert(draft.id.clone())` retain 保留首个同 id(即最小 index) → :548 emit AiToolCallStarted 仅一次。逻辑闭环。
- ②不破坏多工具retain 仅过滤同 id不同 id 工具正常通过stream_recv.rs:225 证实分桶键为 index 非 id。
- ③HashSet importaudit.rs:3 `use std::collections::{HashMap, HashSet}` 正确。
- ④与 F-05 无冲突F-05(audit.rs:612 find_cached_high_risk_result) 按 tool_name+args 去重(不同 id 同命令),本条按 id 去重(同 id),维度正交。
- ⑤前端守卫useAiEvents.ts:205 `if (!findToolCall(event.id))` 确认存在,双保险兜底。
- ⑥cargo checkEXIT 0零编译错误。
- **待修项回流 todo**: **无**(治标守卫 + 治本去重 双保险就位)。
### CR-260616-56 波14 F-04c后端(provider池enabled/weight更新IPC+set_caps启动接入)(b3684f4) — ✅ PASS
- **范围**: workflow wodppv1hd。①ai_save_provider(commands.rs:929-948)编辑路径保 enabled/weight(修原硬编码清回默认 bug)。②ai_update_provider_pool IPC(commands.rs:1022-1056)轻量专用更新 enabled/weight 经 update_full,weight clamp[0,100],落库后 reload_provider_caps 重建 caps。lib.rs:128 注册。③set_provider_caps 启动接入(state.rs:297 AppState::init 调 reload_provider_caps L305-323 从 DB enabled providers 建 per_provider HashMap,cap=global_cap=3)。单点真理 helper(state 不依赖 commands 避免环形)。
- **维度**: ①ai_update_provider_pool 不触 ai_save_provider 密钥迁移分支(避免空 api_key 重发整表) ②reload_provider_caps 单点真理 ③cap=global_cap=3 单 provider 零变化 ④ai_save_provider 编辑保字段(新建默认 true/50)。
- **commit**: b3684f4。
- **主代核查**: cargo check EXIT 0 + 88 passed。
- **复审结论**: PASS。6 维度全核验通过①ai_update_provider_pool 无 api_key 参数不触密钥迁移(commands.rs:1023-1056) ②reload_provider_caps 单点真理(state.rs:311-327) ③global_cap=3 与单 provider 等价(state.rs:321+324-326) ④编辑保留(commands.rs:945-947) ⑤provider_pool::select 过滤 enabled/weight(provider_pool.rs:64) ⑥weight clamp(commands.rs:1037)。无 high/med/low 发现。
- **待修项回流 todo**: **无**(后端就绪,前端 Settings UI 留波16)。
### CR-260616-57 波15 F-04b多provider fallback切换(stream_one_provider+候选链+resolved_model重算)(80c0955) — ✅ 已审(PASS)
- **审查时间**: 2026-06-17(独立审查 agent · 亲跑 cargo check EXIT 0 + grep/read 核验源码)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **逐件核验(全 PASS)**:①stream_one_provider 抽取 ✅(agentic.rs:155-298 封装单 candidate,StreamOutcome 三态 Success/InitFailedExhausted/Fatal 驱动外层切换)/ ②resolved_model 重算 ✅(:188-199 select_model_id 在 candidate.model_configs 上重算+ model_override 穿透,防切换 provider 后模型池不同吃 400/404)/ ③候选链外层 ✅(:663 'candidate for 循环+ :656-658 Vec clone primary 入链避借用冲突)/ ④InitFailedExhausted 切换 ✅(:691-701 continue + :188-199 resolved_model 重算,stream_one_provider 内重建 build_provider)/ ⑤Fatal 立即放弃 ✅(:703-707 guard.reset+return,对齐 retry.rs Fatal 分类 + provider_pool 文档)/ ⑥per-provider permit 切换释放 ✅(:667 acquire_for_provider 循环内取,切换 Drop 自动释放旧槽)/ ⑦retry_deadline 共享预算 ✅(:642 单次 Instant::now+30s,各 candidate 共享不重置,防串行累加超预算)/ ⑧单 provider 零变化 ✅(:352 空池兜底 candidates=Vec::new,candidate_chain 仅 primary,循环跑一次耗尽 return)。
- **退避策略一致性**: ✅ 复用 df_ai::retry::backoff_delay(agentic.rs:14 use df_ai::retry + :270 调用 retry::backoff_delay((retry_attempt+1) as u32)),retry.rs:74-85 实现 1s→2s→4s ±20% jitter,与 CR-30-1 对齐一致。
---
## 已审归档
### CR-260616-34 batch58 F-260616-10 provider 默认值重启失效 bug — ⚠ 已审(PASS-WATCH)
- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码 + 类型确认)
- **总体结论**: ⚠ **PASS-WATCH** — 🔴0 🟡1 ⚪3(核心 bug 双恢复修复 OK,已删除 provider 残留守卫 med 待修)
- **逐件核验**:核心 bug 修复 ✅(重启 activeProvider=null → useAiPanel.ts:53-54 模块级 savedProvider 同步恢复 + loadProviders:101-104 DB is_default 兜底,双恢复链路)/ is_default 字段确认 ✅(types.ts:177 AiProviderConfig.is_default boolean,ai_list_providers 返回)/ cycleProvider 兜底 ✅(AiChat.vue:1184 startId=activeProvider||is_default||null 三级兜底,L1187 Math.max(idx,0) 防 -1 越界)。
- **🟡 CR-34-1(med,已删除 provider 残留未校正)**:useAiPanel.ts:101 `if (!state.activeProvider)` 守卫只查 null 不查有效性。localStorage savedProvider 指向已删除 provider(L54 设 activeProvider=deleted-id)时,loadProviders 守卫 false 跳过 DB is_default 恢复 → activeProvider 残留无效 id → cycleProvider:1185 findIndex=-1 → :1187 next=ps[1] 跳过 ps[0],且潜在消费方(发消息读 activeProvider 调后端)用无效 id。修法:守卫改 `if (!state.activeProvider || !list.some(p => p.id === state.activeProvider))` 残留无效时回落 DB is_default。
- **⚪ low**:1(L52 注释「以 DB 为准」与行为不符,实际 savedProvider 优先 DB 兜底,行为合理注释误导)/ 2(setProvider:121 void appSettings.set 持久化失败静默,仅影响重启恢复可接受)/ 3(多窗口/分离窗口 activeProvider 一致性:模块级 state 各 webview 独立,主 setProvider 写 SQLite 分离窗口 state 滞后,既存架构非 CR-34 引入)。
- **待修项回流 todo**: **CR-34-1**(med,useAiPanel.ts:101 已删除 provider 残留守卫改查有效性)。
---
### CR-260616-33 batch57 CR-260615-04 splitBlocks→marked.lexer() — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3
- **逐件核验(全 PASS)**:splitBlocks(AiChat.vue:671-691 marked.lexer 切块,L672 getMarked 取实例 + L676 未就绪兜底 `return [text]` 不引入正则 + L677 lexer tokenize + L678-689 for 循环)/ code token 整段一块(L681-685 `type==='code'` + raw.trim 守卫空代码块跳过 + continue)/ 非代码 token 按 `\n{2,}` 切段(L688 split + b.trim 守卫)/ 空 token 非 string raw 防御(L679 `typeof string` continue)/ 空兜底(L690 `blocks.length ? blocks : [text]`)/ 签名不变(`splitBlocks(text):string[]` + renderStreamingBlocks:723 调用)/ 切块边界与 parse 渲染边界一致(同 marked 实例同围栏规则,CR-260615-04 根治目标达成)/ 流式路径回归(renderStreamingBlocks:718-733 + parseBlock/parseBlockNoCache 块缓存机制不变)。
- **⚪ low**:1(lexer 全文 tokenize 性能略高于旧正则,rAF 节流+块缓存吸收,流式实测无影响)/ 2(末块未闭合围栏场景 lexer 可能把后续段落吞进 code raw,流式中间态短暂切块不准,末块重 parse 修正)/ 3(L690 空兜底 lexer 空文本返空数组时回退原文)。
- **待修项回流 todo**: **无**(核心目标切块/渲染边界一致达成,防御充分)。
---
### CR-260616-32 batch57 B-260619 Knowledge.vue CSS窄屏标题挤压 — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 vue-tsc EXIT 0 + grep/read 核验源码)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3
- **逐件核验(全 PASS)**:min-width:0 传播链完整(L576 kn-detail-panel → L622 detail-head → L623 detail-title-row flex:1 → L625 detail-title,窄屏标题可正常收缩)/ 760px 断点 column-wrap 与 grid 兼容(L730 kn-layout 单列降级 + L731 detail-head flex-direction:column 无冲突)/ 按钮换行间距(L626 detail-actions gap 6px + flex-wrap:wrap,L732 窄屏 width:100%)。
- **⚪ low**:1(L623 `.detail-title-row { overflow:hidden }` 冗余,word-break 多行标题 height auto 自适应不真裁断,语义不清,建议删保留 min-width:0+flex:1)/ 2(L733 窄屏字号降级仅覆盖 .detail-title 18→15px,编辑态 .edit-input-title L692 固定 16px 未同步,窄屏编辑态 16px 反超展示态 15px)/ 3(18→15px 偏激进,详情主标题窄屏建议 ≥16px)。
- **待修项回流 todo**: **无**(三 low 风格/一致性微调非阻断)。
---
### CR-260616-31 batch56 F-260616-08 分页 offset+探总量 — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码形态;**未亲跑 cargo check**,标准 API 语法无编译风险,待 agent GLM 恢复补 cargo 验证)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **逐件核验(全 PASS)**:①offset 边界(tool_registry.rs:216/237/254 offset `as_u64().unwrap_or(0)`→usize,JSON 负数 as_u64 None→0,skip(offset).take(limit) 超 total 取空不 panic;has_more `(offset+page.len())<total` 超时 false)/ ②total 准确(list_projects:214-215 `list_active().len()` 分页前全量=真实总量;list_tasks:236/list_ideas:253 同)/ ③探总量与 list 一致(get_project_count:1091-1093 list_active().len()=list_projects:214-215 同源;get_task_count:1104-1108 project_id 分支+list_all 兜底=list_tasks:231-234 同逻辑,range 一致)/ ④knowledge_search 解钳(knowledge.rs:110 `.min(20)` 替原 `.min(3)`,允许 LLM 翻更多候选合理)/ ⑤schema 描述(:208/225/246 含 offset/limit/total/has_more 翻页语义 LLM 可理解)。
- **⚪ low**:1(内存放大:list_*/get_*_count 先全量 load(list_active/list_all)再 skip/take,数据量大时内存放大;AI 工具调用 + MAX_LIST_RESULTS 钳 limit 实际可控)/ 2(list_tasks/get_task_count 用 list_all 未排除软删任务,而 list_projects/get_project_count 用 list_active 排除软删,任务与项目软删语义不对称;但 get_*_count 与 list_* 同源一致,且为既存逻辑非 CR-31 引入)。
- **待修项回流 todo**: **无**(五维度全 PASS)。
---
### CR-260616-30 batch56 F-260616-07 流式自动重试 — ⚠ 已审(ISSUES)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check EXIT 0 + cargo test df-ai retry 6 passed **附 log 末尾** + grep/read 核验 + 重试循环正确性专项)
- **总体结论**: ⚠ **ISSUES** — 🔴0 🟡2 ⚪3(核心重试循环逻辑正确 permit/guard.reset/request 重建均 OK,但退避策略注释误称对齐 + AiStreamRetry 死事件 med)
- **亲跑 `cargo check --workspace`** ✅ EXIT 0(8 warning 全既存 dead_code 非本批)+ **`cargo test -p df-ai retry`** ✅ EXIT 0 **6 passed**(backoff_is_monotonic_within_jitter/status_classification/fatal_aborts_immediately/first_attempt_ok/retry_once_then_ok/all_retryable_exhausts,finished 3.35s)。
- **重试循环正确性专项(全 PASS)**:permit 持有释放 ✅(`_global_permit`/`_per_conv_permit` agentic.rs:239-240 外层函数体作用域,耗尽 return:285/成功 drop:333-334 均 RAII 释放无并发槽泄漏,重试期间持 permit 有意防新请求挤占)/ 耗尽路径 guard.reset ✅(:284 `guard.reset().await` return 前显式复位 generating,max_retries=0 时 `0..=0` 单次失败 is_last→reset+return 边界正确)/ request 每轮重建 ✅(:248-263 每轮 `session_arc.lock()`+`build_for_request` 重建 messages 无累积/丢失,与循环外:207-215 `request` 变量重复轻微冗余无害)。
- **🟡 CR-30-1(med,退避策略注释误称对齐)**:agentic.rs:298 `1u64 << retry_attempt` 纯指数无 jitter/无总预算/一律重试(含 MidStream);retry.rs:67-78 `backoff_delay` 有 ±20% jitter + 30s 总预算(:34)+ 4xx 不可重试分类(:102-104 Fatal 立即放弃)。三处注释(agentic.rs:37/commands.rs:1106/state.rs:185)均称「对齐 retry.rs 退避策略(1s→2s→4s+jitter)」与实现不符。修法:要么 agentic 加 jitter+总预算+错误分类真对齐,要么删注释「对齐 retry.rs」改为「独立简化策略」。
- **🟡 CR-30-2(med,AiStreamRetry 死事件 + MidStream 重试文本混乱)**:后端 emit AiStreamRetry(agentic.rs:310),types.ts:214 有 variant,但 useAiEvents.ts switch 无 `case 'AiStreamRetry'`(十 case 无 retry)事件穿透忽略。更严重:MidStream 失败重试时已 emit 的 AiTextDelta 累积进前端 currentText,重试新 delta 追加 → 文本重复/混乱(stream_recv.rs:210/240 MidStream return None,agentic.rs:294 一律重试无清空 currentText 机制)。修法:useAiEvents 补 case + 重试前清空 currentText;或后端 MidStream 失败不重试只重试 Init 失败(对齐决策 a 只重试流前失败)。
- **⚪ low**:1(agentic.rs:243/317 `last_error` 声明赋值但 result 分支:323-330 未读 dead code)/ 2(:294-296 MidStream 一律重试注释承认「简化」,与决策 a 只重试流前失败相悖)/ 3(run_agentic_loop 3 调用点 commands.rs:108/204/444 非登记「4 入口」,try_continue 走审批恢复复用非独立入口)。
- **待修项回流 todo**: **CR-30-1**(med,退避策略注释对齐)+ **CR-30-2**(med,AiStreamRetry 死事件+MidStream 重试文本混乱)。
---
### CR-260616-29 batch56 ARC-06 composable 循环依赖破环 — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验 import + 源码)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪1
- **逐件核验(全 PASS)**:①循环依赖真破(useAiEvents.ts import L14-23 无 useAiSend,改 L284 `emit('ai-drain-queue')` 事件总线;useAiSend.ts:25 仍 import useAiEvents(startListener)单向,双向环已断)/ ②事件总线桥接(useAiEvents:284 emit + useAiSend:395-398 initDrainQueueListener `if(_drainUnlisten) return` 幂等守卫 + stores/ai.ts:134 void 注册)/ ③findToolCall 下沉一致(aiShared.ts:41-51 反向扫 messages + 同消息正向扫 toolCalls,两调用方 import 同一实现,注释:37-40 说明反向扫描避免陈旧引用)/ ④startApprovalTimer 幂等(aiShared.ts:66 `if(_approvalTimers.has) return` 同 id 不重建 + clearApprovalTimer/clearAllApprovalTimers 清理完整)。
- **⚪ low**:1(事件总线桥接理论竞态:initDrainQueueListener stores/ai.ts:134 void 不 await,注册前若 AiCompleted 到达事件丢失 drainQueue 不触发;实际初始化远早于首次生成完成低风险,原 direct call 同步无此问题,可靠性微小降级)。
- **待修项回流 todo**: **无**(四维度全 PASS,循环依赖真破)。
---
### CR-260616-28 batch56 F-260616-06 审批幂等+防抖 — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码 + 方法存在性确认)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **逐件核验(全 PASS)**:①幂等三态覆盖(commands.rs:220 内存 pending_approvals.remove None → :224 查审计表 find_by_tool_call_id → :227 executed/rejected/failed 返回 Ok「已处理」三态全覆盖;pending 态落 :231 Err 合理——内存 pending_approvals 丢失即审批上下文不可恢复,DB pending 占位仅日志非可恢复审批)/ ②防抖(useAiSend.ts:307 _pendingApprovalIds.has 守卫无时间窗口竞态 + :308 add + :332-334 finally delete 无论成功异常均清理,模块级 Set 不泄漏)/ ③find_by_tool_call_id 存在确认(crud.rs:1198-1201 `pub async fn(&self,&str)->Result<Option<AiToolExecutionRecord>>`,:224 unwrap_or_default Err→None→:231 Err,DB 故障不误判已处理)。
- **⚪ low**:1(pending 审计记录 + 内存 pending_approvals 丢失→:231 Err「未找到挂起的审批」文案略困惑,实际审批上下文已不可恢复 Err 正确,文案对有 pending 审计记录场景不精确)/ 2(防抖 _pendingApprovalIds 模块级跨调用累积,finally 清理可靠无泄漏,机制说明非问题)。
- **待修项回流 todo**: **无**(三维度全 PASS)。
---
### CR-260616-27 batch54 复核-新⑪ detach 清主窗口 state 看门狗幽灵(useAiWindow.ts) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验 + vue-tsc EXIT 0)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **逐件核验(全 PASS)**:detachPanel(useAiWindow.ts:28-38 快照在清 state 前写入 — L28-31 localStorage.setItem df-ai-gen/df-ai-text → L37-38 清 state.streaming/generatingConvId,顺序正确)/ state 独立性(主/分离窗口独立 webview JS context,resumeInDetached:87-119 靠 localStorage df-ai-gen/df-ai-text 重建 streaming L116-117,反证清主窗口 state 不影响分离窗口生成态)/ watchdog 兜底(stopListener clearStreamWatchdog 已由 CR-260615-24 兜底,本次仅清 state 视觉残留,注释:32-36 说明链路)/ reattach/closeDetachedWindow 清快照(L82-83/L125-126)。
- **待修项回流 todo**: **无**(全件 PASS)。
---
### CR-260616-26 batch53 F-05 去重根治(audit.rs High risk 审批前缓存命中去重) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 **附 log 末尾证据** + grep/read 核验全件 + 边界核验)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo Finished 0.68s + vue-tsc log 空;5 warning 全既存 dead_code 非本批)。**无中间态误报**(附 log 末尾)。
- **逐件核验(全 PASS)**:find_cached_high_risk_result(audit.rs:400-458 反向扫 `msgs.iter().rev()` + tool_name 过滤 :423 + canonical_args_key 规范化比较 :430 + 旧 id 反查 tool_result :444-456)+ High 分支查缓存命中回传(:536-553 guard High → push tool_result 新 tool_call_id :544 + emit AiToolCallCompleted :545-549 + 审计 completed/auto_dedup :551 + continue :552 跳过 pending insert)+ 仅 High 去重(:534 Medium|High 共分支 + :536 内嵌 High guard,Med 走原审批 :555-573)+ pending 占位不命中(:452 占位串「需要用户审批等待确认」return None,与 push :564 全角逗号一致)+ args 规范化(sort_object_keys :462-492 sort_by 字典序 + 递归值/Array + to_string 紧凑稳定)+ 不同命令/参数不命中(timeout 重发可重试)+ tool_call_id 来源修正(stream_recv.rs:181 draft.id 来自 tc_delta.id 非 stream_recv UUID)+ 零越界(纯 audit.rs 3 函数 + High 分支,未碰 batch42/44/51)。
- **关键边界核验**:二次循环自吞(第一次重试命中缓存 push tool_result → 第二次重试 find_cached 反扫命中第一次 tool_call 又跳过审批)= **期望行为**(每次重试跳过审批断循环,非 bug)。
- **⚪ 观察项(全 low)**:OBS1(canonical_args_key to_string unwrap_or_default 理论序列化失败空串误等价,实际 JSON 不会失败)/ OBS2(空参数同名 High 工具误命中,当前工具集无)/ OBS3(去重命中 tool_result 下轮被命中 = 期望行为断循环)。
- **待修项回流 todo**: **无**(全件 PASS,3 OBS 全 low)。完整 agentic loop 通用去重(search_files 重复卡片 + 滑动窗口)③类待用户,本批仅 run_command 循环核心。
---
### CR-260616-25 batch52 UX-10 @实体引用全栈(@ popover + prompt 任务段注入) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 **附 log 末尾证据** + grep/read 核验全件)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双真实 EXIT 0(cargo Finished 0.72s + vue-tsc log 空 0 error;5 warning 全既存 dead_code 非本批)。**无中间态误报**(附 log 末尾,呼应 CR-23-1 教训防 E0594 类瞬态漂移)。
- **逐件核验(全 PASS)**:@ popover 复用 `.ai-skill-popover`(AiChat.vue:563)+ 联想状态三件(mentionOpen/Index/Start :992/993/995 独立于 skill)+ detectMentionTrigger 邮箱排除(:927-953 prev 非空白 break)+ selectMention `[类型: 名]` 插入(:1057-1079)+ mentionItems 项目+任务各 slice(0,20)(:1009-1037)+ mentionGroups 分组(:1046-1049)+ mentionTotal 空/无匹配区分(:1040-1043)+ **键盘导航与技能联想互斥**(:902-920 watch inputText skill 块优先强制关 mention + selectSkill/selectMention 互清,skillOpen 与 mentionOpen 不可同时 true)+ Enter 不冲突(:1433-1456 popover 开 selectMention preventDefault 覆盖发送,关 fallthrough handleSend)+ prompt.rs:117-126 任务段(state.tasks.list_active take(20) + 注入 title/status/description 镜像项目段范式 :108-115)+ 签名零变化(build_system_prompt :102 4 调用方 commands.rs:69/161/391 + agentic.rs:478 零改)+ 降级合理(prompt 走后端 state repo :119 非前端 mentionEntities 透传,避碰 batch45/48/49/51 领地)+ 实体 id 未透传(仅 `[类型:名]` 文本,重名任务风险低已知降级)+ i18n 5 key 双语(aiChat.ts:76-80)。
- **⚪ 观察项(全 low)**:OBS1(mentionHint 死键定义无 .vue 消费,建议删或激活)/ OBS2(mentionQuery :1003-1006 slice 到串尾非光标前,光标中移时偏差,常规用法无影响)/ OBS3(desc 仅 popover 展示不进 prompt,prompt 任务段用 tk.description 摘要列间接影响 LLM,语义链路清晰)。
- **待修项回流 todo**: **无**(全件 PASS,3 OBS 全 low)。
---
### CR-260616-24 batch51 UX-09 编辑已发送消息并重新生成全栈(messages JSON status + ai_chat_edit IPC + AiChat 编辑 UI) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验全件 + **CR-23-1 独立核验**)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪4
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo Finished 0.84s,5 warning 全既存 dead_code 非本批)。
- **🔴 CR-23-1 独立核验:不成立** ✅ — `context.rs:478-495` 当前是 **for 循环**(L488 `for t in ...iter_mut()` + L490 `t.message.status=Some("truncated")` + L491 `count += 1;` 计数非 filter 闭包体),workspace cargo check 真实 EXIT 0(全量非分 crate)。原 CR-23 agent 报 E0594 是**中间态误报**(batch51 最终 for 循环规避双层引用,呼应 CR-21/18/16 瞬态漂移)。**主代理声明属实,CR-23-1 标无效**。
- **逐件核验(全 PASS)**:provider.rs:54-55 ChatMessage `status: Option<String>`(serde default+skip_serializing_if 向前兼容 None=active 无 V17 迁移)+ 5 构造函数补 None(:60/63/66/69/72)+ is_active()(:76-77)+ sanitize_messages step0 过滤 truncated(context.rs:287-296)+ replace_last_active_user_content(:501-512 rposition+token 重估)+ truncate_after_user_message(:478-495 for 循环)+ ai_chat_edit IPC(commands.rs:351-445 复用 ai_regenerate spawn 模式:generating 拦截:363+活跃一致:367+replace 末条 user:371-377+truncate 其后:379-382+save:418+spawn:428-442)+ 只末条 active user 可编辑(replace rposition + Err)+ title.rs:51 + lib.rs:108 + 前端 api/ai.ts:33 editMessage + useAiSend.ts:212-270(splice 截断+改 content+空气泡占位+IPC+失败回滚)+ useAiConversations.ts:72 filter truncated + AiChat.vue:329-337 编辑按钮 hover(isLastUser && !streaming)+ isLastUser/startEdit/cancelEdit(:1028-1059)+ handleSend 编辑分支(:1267-1285)+ ESC 取消(:1211-1215)+ 切换取消(:883-885)+ i18n 4 key 双语(aiChat.ts:100-103)。
- **⚪ 观察项**(全 low):OBS1(导出 batch46 含 truncated 软删历史,markdown/txt 视觉见旧回复段,完整审计语义取舍)/ OBS2(truncate_after_user_message/replace_last_active_user_content 无单测,token 计数同步易错建议补 case)/ OBS3(useAiSend editMessage convId 空回滚不彻底,极端场景后端未触发 IPC)/ OBS4(editMessage 无 generating 前端预检,DOM 隐藏+后端拦截双兜底无并发风险)。
- **待修项回流 todo**: **无**(CR-23-1 标无效,4 OBS 全 low 非阻断)。
---
### CR-260616-23 batch50 审查 med 修复聚合(CR-15-1 kill_on_drop + CR-08-O1 blocked 排除 + CR-10-A/B 停下报告) — ⚠ 已审(ISSUES)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check EXIT 101(撞同攒批 CR-23-1)+ vue-tsc EXIT 0 + grep/read 核验四件)
- **总体结论**: ⚠ **ISSUES** — 🔴1 🟡0 ⚪1(三件声明项全 PASS,但同攒批工作区 context.rs:491 编译失败阻断整批)
- **亲跑 `cargo check --workspace`** ⚠ **EXIT 101**(df-ai 编译失败,CR-23-1,非三件范围)/ `npx vue-tsc --noEmit` ✅ EXIT 0。
- **三件声明项核验(全 PASS)**:
- **CR-15-1 kill_on_drop** ✅ `shell.rs:85` `.kill_on_drop(true)` match 块外统一调用,三类型(PowerShell:62/Cmd:68/Sh:74)全覆盖,注释:81-84 配合 tokio::time::timeout:96-101 防孤儿;正常完成路径 cmd.output() 已读完 output 自然 drop 无害 — 不破坏 cmd.output() 成立。
- **CR-08-O1 blocked 排除** ✅ `TaskDetail.vue:265-274` `WF_EXCLUDED_FROM={'blocked'}` :265 + wfAdvanceAction computed :269 `if has return null`,DOM :71 v-if 不渲染。**不动 ADVANCE_MAP**(:219-246 五态原推进项全保留),其他态不受影响。
- **CR-10-B 否决** ✅ **同意主代理「不成立,审查锚定漂移」**:独立 Read commands.rs:37-110,L94 save_conversation 锁外(L46-65 锁块 L65 关闭),L105 spawn 在 save 后,时序正确。L56/59 是 pop 失败防御性错误处理(复位 generating + 报错)非 save/emit 时序。**原 CR-10-B 把 pop-失败-错误分支误读为时序冲突,审查锚定漂移坐实**。
- **CR-10-A 降级 P2** ✅ 三入口(ai_regenerate:37/ai_chat_send:128/ai_chat_force_send:453)统一「锁内变更+锁外 spawn」,兜底充分(generating 占用 busy 拦截 + stop_flag 复位 + force_send:460-468 复位三件套 + ai_chat_stop:520-536 3s 兜底任务防 panic/异常退出卡死),接受现状合理。
- **🔴 CR-23-1(high,编译失败,同攒批非三件范围)**:`crates/df-ai/src/context.rs:491` E0594 `cannot assign to t.message.status, which is behind a & reference``truncate_after_user_message``self.messages[i+1..].iter_mut().filter(|t| {... t.message.status=...})` filter 闭包参数 t 是 `&&mut TrackedMessage`(双层引用)不能写字段。git stash HEAD EXIT 0 / pop 工作区 EXIT 101 → UX-09 新增代码引入。**阻断整个 df-ai crate 构建,下游 commands.rs/agentic.rs 全 fail**。修法:filter 改 for 循环 + 计数,或闭包内 `let t=&mut **t;` 解引用。
- **⚪ 观察项**:OBS1(agent 自报 cargo EXIT 0 与实际 EXIT 101 不符,可能跑 HEAD 或分 crate,结论可信度打折,建议后续 agent 自报附 log 末尾证据)/ OBS2(CR-23-1 非三件范围但同攒批,合并前必先修否则整批构建失败)。
- **待修项回流 todo**: **CR-23-1**(high,context.rs:491 编译失败,改 for 循环或解引用,阻断 df-ai 构建 — **紧急,主会话合并前必修**)。
---
### CR-260616-22 batch49 UX-17 对话置顶全栈(pinned 字段+迁移+IPC+排序+图钉按钮) — ✅ 已审(PASS-WATCH)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + cargo test df-storage + vue-tsc 三 EXIT 0 + grep/read 核验 14 件)
- **总体结论**: ⚠ **PASS-WATCH** — 🔴0 🟡1 ⚪3(CR-22-1 med 已置顶图钉非 hover 不可见)
- **亲跑 `cargo check --workspace` + `cargo test -p df-storage` + `npx vue-tsc --noEmit`** ✅ — 三 EXIT 0(cargo 9 warning 全既存 dead_code;df-storage 22 单元 + 11 集成 passed;vue-tsc 0 error)。
- **独立核验**(14 件):models.rs:163 `pub pinned: bool`(非 Option 必填,list 摘要手写 json! snake_case 对齐)/ crud.rs:1141-1148 INSERT 含 pinned(?8)+ :1153-1160 UPDATE 含 pinned(?7)params 对齐 / :1050 ai_conversation_from_row 读 pinned i32→bool / :1657-1672 set_pinned 仅 SET pinned 不动 updated_at(纯元数据)/ migrations.rs:285-296 V16 column_exists 守卫幂等 + ALTER ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0(老库非 NULL)+ :53 注册 (16, migrate_v16)/ commands.rs:849-859 ai_conversation_set_pinned IPC / :746 list 摘要 json! 加 pinned / lib.rs:128 注册 / **conversation.rs:141 pinned: false(proactive-bug-fix 坐实:models.rs:163 非 Option 必填,insert 不补编译断 E0063;update :108 从库 get rec.pinned 回写不丢失)** / AiChat.vue 排序 :1054(groupedActive)+ :1069(archivedConvs)`Number(b.pinned?1:0)-Number(a.pinned?1:0)||Number(b.updated_at)-Number(a.updated_at)` pinned DESC→updated_at DESC + V8 stable / :53/120/162 三处图钉按钮 toggle / i18n zh:9-10/en:9-10 双语 / useAiConversations.ts:174-178 setPinnedConversation IPC+本地同步 / api/ai.ts:156-157 + types.ts:246。
- **🟡 CR-22-1(med,UX 缺陷)**:AiChat.vue:1872-1873 `.ai-conv-item-actions { display:none }` + :1878 `:hover``display:flex` → 所有操作按钮(含图钉)默认隐藏,:1903 `--pinned` 仅改 color 未覆盖 display → **已置顶对话非 hover 态图钉不可见**,「常驻强调色一眼识别」UX 意图落空。功能(toggle/排序)正常。修法:`.ai-conv-item-act--pinned``display:flex`。登记自相矛盾(hover 浮出 toggle 与常驻强调色无法同时满足),需产品定调。
- **⚪ 观察项**:OBS1(filteredConversations 搜索态未加 pinned 排序,置顶不前置,登记自认降级可接受)/ OBS2(V16 无独立测试靠 column_exists 探测,项目一贯模式)/ OBS3(update_full:1151 读库 rec.pinned 回写不丢失,未来绕过 get 路径风险当前无)。
- **待修项回流 todo**: **CR-22-1**(med,AiChat.vue:1872-1903 已置顶图钉非 hover 不可见,「常驻强调色」UX 未达成,补 display 规则或调 actions 可见性策略)。
---
### CR-260616-21 batch48 UX-18 对话导出前端(exportConversation 封装 + 3 处导出按钮 + Blob 下载) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 vue-tsc + cargo check 双 EXIT 0 + grep/read 核验 12 件)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3
- **亲跑 `npx vue-tsc --noEmit` + `cargo check --workspace`** ✅ — 双 EXIT 0。**vue-tsc EXIT 0 确认 batch48 完成后 `toggleExportMenu/exportingId/exportOpenId/exportConversation` 均已定义,CR-15/16 agent 早报的 12 TS2339 瞬态已消除**(cargo 5 warning 全既存 dead_code NodeExecutionRepo/ReleaseRepo 非本批,本批纯前端)。
- **独立核验**(12 件):api/ai.ts:165-167 exportConversation 封装(camelCase 仿 continueLoop/stopLoop,签名 `(conversationId, format): Promise<string>`,invoke `ai_conversation_export` snake→camel 对齐 commands.rs:872)/ AiChat.vue 3 处导出按钮 DOM(搜索态 :59-68 / 活跃态 :126-135 / 归档态 :168-177,每处 pin→archive→export→delete 顺序,@click.stop 阻冒泡,:disabled=exportingId===conv.id 防重复)/ `:last-child` 红色态保留(:1898-1901 delete 是末子 export 倒数第二,精确命中删除按钮不被破坏)/ toggleExportMenu/exportOpenId 单展开(:912-917 三元切换同时一开)/ Blob 下载 + revokeObjectURL(:941-950 setTimeout(0) 下轮事件循环释放防泄漏,mime 三格式精确 text/markdown|application/json|text/plain)/ sanitizeFilename 安全(:920-927 空 trim→fallback conv-id + replace 折叠 Win/Unix 路径分隔符+非法+控制字符 + slice(0,60) 限长 + 全清空降级)/ exportingId 防重复(:913/931-932/956 入口 guard+set+finally 三段)/ onExportOutsideClick + 生命周期(:961-966 早返回+closest 防误关 + :1381/1317 add/remove 对称)/ IPC catch 兜底(:951-954 e.message||String(e)+toast 不崩溃)/ i18n 5 key 双语(:112-116 exportFailed 含 {msg})/ 消费 batch46 IPC(commands.rs:872 + lib.rs:129)/ 零越界(batch45 continueLoop/stopLoop :37/45 + batch43 搜索框 :23-34 + batch47 确认 handler :819-841 独立区未改)。
- **⚪ 观察项**:OBS1(revokeObjectURL setTimeout(0) 下载时机,社区惯例无可见问题)/ OBS2(菜单无 Esc 关闭,onGlobalKeydown Esc 已消费他途,UX 小瑕疵)/ OBS3(filename 无时间戳,同名浏览器自动加后缀,设计取舍)。
- **待修项回流 todo**: **无**(12 件全过,双 check EXIT 0,零越界)。
---
### CR-260616-15 batch42 F-04 run_command 超时根治(tool_registry.rs 超时标注 + 常量提取) — ✅ 已审(PASS-WATCH)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo EXIT 0 + vue-tsc EXIT 2 非本批 + grep/read 核验 8 件)
- **总体结论**: ⚠ **PASS-WATCH** — 🔴0 🟡1 ⚪2(CR-15-1 med shell.rs kill_on_drop 缺失)
- **亲跑 `cargo check --workspace`** ✅ — EXIT 0(本批纯 Rust tool_registry + shell)。**`npx vue-tsc --noEmit`** ⚠ EXIT 2(12 errors 全在 AiChat.vue 导出功能 toggleExportMenu/exportingId/exportOpenId/exportConversation,系并发 batch48 未完成实现,与本批 F-04 Rust 零关联)。
- **独立核验**(8 件):tool_registry.rs:24 常量 `DEFAULT_RUN_COMMAND_TIMEOUT_SECS=60` / :495 unwrap_or 用常量单一使用点 / :510-520 map_err 拦超时改写文案(contains 匹配 shell.rs:95 产出串前缀一致)/ shell.rs:89-95 超时根因点(`tokio::time::timeout``cmd.output()`)/ commands.rs:251-256 High 审批路径 `e.to_string()` 透传 / audit.rs:450 Low/并行路径同源(经 ai_tools.execute 分发无遗漏)/ commands.rs:269 replace_tool_result_content 断重试链路(LLM 收改写文案不再触发新 tool_call_id 重审批)/ tool_registry.rs:472 run_command 单注册。
- **🟡 CR-15-1(med,真 bug)**:`crates/df-execute/src/shell.rs:60-79` 构造 `tokio::process::Command` **缺 `.kill_on_drop(true)`**(tokio 1.52.3)→ `tokio::time::timeout` drop future 后 tokio **不主动 kill 子进程**,子进程变**孤儿继续后台跑**,但 `tool_registry.rs:514` 文案称「进程已终止」**名不副实**。长 hang 命令(大构建/死循环)超时后仍耗资源。治本应 shell.rs:60-79 构造 cmd 后加 `.kill_on_drop(true)`。根因层缺陷(非本批 tool_registry 标注层引入),但本批文案依赖该假设。
- **⚪ 观察项**:OBS1(tool_registry.rs:473 描述 + :494 注释硬编码「默认 60 秒」字面量未引用 const,改常量需手改两处易漏)/ OBS2(const :24 模块私有,shell.rs 未共享)。
- **待修项回流 todo**: **CR-15-1**(med,`crates/df-execute/src/shell.rs:60-79``.kill_on_drop(true)`,孤儿进程 + 文案「进程已终止」语义不实)。
### CR-260616-16 batch43 UX-06 对话搜索 UI 接线(AiChat.vue 侧栏) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo EXIT 0 + vue-tsc 首跑 EXIT 2 缓存陈旧清缓存重跑 EXIT 0 + grep/read 核验 8 件)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3
- **亲跑 `cargo check --workspace`** ✅ — EXIT 0(7 warning 全既存 dead_code)。**`npx vue-tsc --noEmit`** 首跑 EXIT 2(6 TS2339 全在 AiChat.vue:57-63 导出功能 toggleExportMenu 等,属并存 batch48 导出 UI 非本批)→ 清增量缓存重跑 **EXIT 0**,确认缓存陈旧 quirk 非真实错。
- **独立核验**(8 件):搜索框 DOM(AiChat.vue:23-34 .ai-conv-search + v-model + kbd Ctrl+K)/ Ctrl+K 绑定(:996-1003 preventDefault + 侧栏未开 toggleSidebar + nextTick focus,顺序 Ctrl+L→K→B→Esc 无重复)/ keydown listener 生命周期对称(:1273 注册 / :1211 移除)/ searchInputRef(:847)/ 平铺渲染(:36-74 template v-if searchResults + :key 's+' 防键冲突 + 空态 searchEmpty + v-else 原分组逐字保留 editingConvId 双击改名零回归)/ searchResults computed(:974-976 显式 .value 解包 ComputedRef 坑已修)/ 消费 batch41 store 契约零改(CR-14 已审 store 侧)/ i18n 3 key 双语(:13-15)。Ctrl+K 冲突核验:grep 无既有 Ctrl+K,Tauri WebView2 无地址栏。
- **⚪ 观察项**:OBS1(Ctrl+K 输入焦点抢占,打字时按 K 切走光标,UX 选择非破坏)/ OBS2(kbd 与 title 复用同一 i18n key,tooltip 语义弱)/ OBS3(范围声明漂移:搜索态平铺项被 batch48 加 export 菜单,审查时点与现状漂移,归 batch48 审)。
- **待修项回流 todo**: **无**(vue-tsc EXIT 2 缓存陈旧非真实错)。
### CR-260616-17 batch44 F-03 达 max 后端(agentic 暂停态 + AiMaxRoundsReached 事件 + continue/stop IPC) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验 6 件 + 交叉核验)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 7 warning 全既存 dead_code)。
- **独立核验**(6 件 + 交叉):agentic.rs:322-347 达上限改(save 先行 :340 + disarm :342 + emit AiMaxRoundsReached :343-345 + return :346 保 generating 暂停态,对齐审批等待模式 L313-316)/ mod.rs:117-122 variant + `#[serde(tag="type")]` / commands.rs:446-465 ai_continue_loop(generating + active 双校验 + 复位 stop_flag + try_continue)/ commands.rs:476-502 ai_stop_loop(双校验 + 置 stop_flag 双保险 + generating=false + emit AiCompleted 不重复 save)/ lib.rs:112-113 注册 / 决策 a(loop 局部 iteration agentic.rs:159,续跑 try_continue 重 spawn 天然重计)。交叉:try_continue guard(should_continue)、ai_chat_stop 暂停态交互(走 3s 兜底正确非最优)、ai_chat_force_send(复位 generating 清审批走新 loop)、stop_flag 双保险(continue 复位 :460 + stop 置 :490)。
- **⚪ 观察项**:OBS1(ai_chat_stop 暂停态落流式分支走 3s 兜底非最优,前端延迟,建议审批分支前加暂停态判定即时清理,既存 stop 路径未适配新状态)/ OBS2(无硬上限产品风险,前端操作卡建议多次继续后二次确认)。
- **待修项回流 todo**: **无**
### CR-260616-18 batch45 F-03 达 max 前端操作卡(pendingMaxRounds + 续/停按钮) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验 6 件 + 死锁风险核验)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code)。
- **独立核验**(6 件 + 死锁):types.ts:210-211 AiMaxRoundsReached variant 联合 + serde 对齐 / useAiEvents.ts:93 pendingMaxRounds 模块级 ref 独立事件源 + :200-204 dispatch + :286/320 AiCompleted/AiError 双清 + :41 NO_RESET_WATCHDOG 含 / api/ai.ts:37-38/45-46 continueLoop/stopLoop 仿 ai_approve / AiChat.vue:468-483 操作卡 + :1625-1626 showMaxRoundsCard 双守卫 + :1632-1658 handler IPC 失败回滚 acting + :1660-1663 watch 复位 + :2384-2395 样式 / i18n 6 key 双语(:102-107)/ 后端契约对齐(agentic.rs:343 + commands.rs:446/476 + lib.rs:112-113)。死锁核验:try_continue 三路径(should_continue=false 补 AiCompleted / provider-Err emit AiError / 正常 spawn run_agentic_loop)全覆盖,所有出口触发清 pendingMaxRounds + watch 复位 acting,无死锁。
- **⚪ 观察项**:OBS1(无 max 暂停态查询 IPC,跨重启恢复缺失,设计取舍 force_send 可复位)/ OBS2(continue/stop IPC 命名风格微异:ai_approve 动词 vs continue/stop 动词+名词)。
- **待修项回流 todo**: **无**
### CR-260616-19 batch46 UX-18 对话导出后端(ai_conversation_export IPC) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check EXIT 0 + grep/read 核验 11 件)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2
- **亲跑 `cargo check --workspace`** ✅ — EXIT 0(workspace 干净,0 warning 0 error)。
- **独立核验**(11 件):conversation.rs:19/24/48 确无导出 IPC(只 TokenAccumulator + save_conversation)/ IPC 位置 commands.rs:853-913 / format 双保险防 panic(:860-863 `matches!` 前置 Err + :908-909 `_ => Err` 末尾)/ 落库 messages serde_json::from_str 零内存读(:866-871 与 switch 一致)/ markdown 四臂 match 真穷举无 `_` 通配(:878-883 MessageRole 4 variant 编译器强制)/ markdown content 原样三反引号不转义(:886)/ json to_string_pretty(:892-893)/ txt 仅读 m.content 略 tool_calls/tool_call_id/model(:898-904)/ 空对话 markdown/txt 空串 json `"[]"`(:889/906/892)/ lib.rs:128 注册(与 batch44 L112-113 不同行)/ 零越界(与 batch44 continue-stop 不同段)。
- **⚪ 观察项**:OBS1(json 空对话返 `"[]"``""`,语义合理前端 JSON.parse 正常)/ OBS2(markdown 顺序依赖落库保序约定)。
- **待修项回流 todo**: **无**
### CR-260616-20 batch47 UX-08 新建对话确认弹窗 — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 vue-tsc + cargo check 双 EXIT 0 + grep/read 核验 10 件)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **亲跑 `npx vue-tsc --noEmit` + `cargo check --workspace`** ✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code NodeExecutionRepo 非本批)。
- **独立核验**(10 件):confirmNewConversation handler(AiChat.vue:832-837 非生成态零行为变化直接 newConversation)/ isViewingGenerating 双条件(:965-967 streaming && generatingConvId===activeConversationId,切走后台不弹)/ store 三字段(ai.ts:47/51/59)/ 两处按钮改造(:18 侧栏 + :206 聊天头,登记写 L176 实际 L206 行号偏差)/ ConfirmDialog 复用(:812 useConfirm Promise + 并发覆盖防永挂 useConfirm.ts:39)/ store.newConversation 软复位(useAiConversations.ts:31-41 B-260615-10 零改)/ commands.rs 零改(git diff 仅 batch46 ai_conversation_export)/ i18n zh/en(:84)/ Esc 优先级协同(:1099-1105 confirmState.visible 时 Esc 视取消 resolve Promise 不卡死)/ 零越界(batch43/45/36/47 独立区块)。
- **待修项回流 todo**: **无**(W-1 low 范围偏差 L176→L206 代码正确)。
---
### CR-260616-14 batch41 Sidebar+ai store 域(UX-06 store 契约 + B-45 内存泄漏核查) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 7 件)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3(filteredConversations .slice() 冗余 + sort 稳定性依赖 + 范围声明与实际不符,均 low 非阻塞)
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 4 warning 全既存 dead_code 非本批;vue-tsc 零 TS error,AiChat.vue:940 `computed<AiConversationSummary[] | null>` 类型精确匹配)。
- **独立核验**(7 件):
- **filteredConversations 契约**(stores/ai.ts:118-125)✅ — 空 query trim→null 信号(消费方走原分组)/ 非空 `filter((c.title ?? '').toLowerCase().includes(q))` case-insensitive 全量(活跃+归档)/ updated_at DESC(b 在前为正,正确)。
- **类型契约**(api/types.ts:237-247)✅ AiConversationSummary 含 title?/archived/updated_at 类型安全。
- **useAiStore 返回值**(stores/ai.ts:136)✅ filteredConversations 已展开。
- **i18n 3 key**(zh/en aiChat.ts:13-15)✅ searchPlaceholder/searchEmpty/searchShortcutHint 双语对称。
- **UI 接线已落地**(AiChat.vue:22-64/939-941)✅ — v-model 双绑 + 搜索态平铺 + 空态提示 + computed 解包 store.filteredConversations.value;搜索项含 archive/delete 操作与原分组对称(登记称「留 batch43」,实际 batch41 已完整接线)。
- **B-45 stopListener**(useAiEvents.ts:344-354)✅ — 三 listener unlisten + 置 null + clearStreamWatchdog + clearAllToolSlowTimers + clearAllApprovalTimers 五件全清。
- **B-45 AiChat onBeforeUnmount**(AiChat.vue:1172-1191)✅ — removeEventListener keydown + cancelAnimationFrame + 拖拽 listener + body class + store.stopListener() + _unlistenToolSlow + _toastTimer/_titleFlashTimer/_providerBarTimer 八件全清。
- **MESSAGE_CAP=200**(stores/ai.ts:36/94-105)✅ — watch 拦 push 式增长(>CAP)splice 最早条;切对话整体替换放行。
- **⚪ 观察项**(均 low):
1. OBS1(冗余 slice):stores/ai.ts:123 `.slice()` 紧跟 `.filter()`,filter 已返新数组,slice 冗余拷贝。无功能影响,建议删。
2. OBS2(sort 稳定性):updated_at 相等返回 0 依赖 V8 ES2019+ stable sort。会话同秒 updated_at 概率低,无可见问题。
3. OBS3(范围声明不符):登记称「UI 接线留 batch43」,实际 batch41 已完整接线。建议主会话核对 batch43 是否剩 i18n 微调。
- **待修项回流 todo**: **无**(三观察项均 low 非阻断)。
### CR-260616-13 batch40 CR-06 审查观察项测试补强(②-3/②-4 回调测试 + ignored 确认) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo test + grep/read 核验)
- **总体结论**: ✅ **PASS** — 🔴0 🟡1⚪1(in_progress 失败→todo 状态机拦截产品决策点 med 非阻断 + 测试数登记 +9 vs +10 low)
- **亲跑 `cargo test -p df-nodes -p df-workflow`** ✅ — df-nodes **67 passed + 1 ignored**(57→67,+10 测试)/ df-workflow 23 passed,双 EXIT 0。
- **独立核验**:
- **ignored 确认**(ai_node.rs:298-300)✅ — glm_live_complete 真调 GLM 集成测试,合理 #[ignore] 保留。
- **9 新测试覆盖** ✅ — 3 templates callback(task_workflow_templates:callback 边界 + reject 可达性)+ 6 advance callback(task_advance_node:regression_target 镜像契约 + 三段 completed→target/failed→regression/非终态跳过 + 落库验证);镜像契约机制防 workflow.rs:44-51 私有函数回归漂移。
- **🟡 观察项 O1(med,产品决策点,非本批引入)**:**in_progress 模板失败→regression_target 给 todo,但状态机 `backward_to_todo_rejected` 全拒**(task_state_machine.rs:188-193)→ 回调 advance_task_atomic 被 InvalidState 拦截(task_advance_node.rs:72-77),**任务原地保留 in_progress**。三选项待定:a 接受现状补 UI 提示 / b 改 `regression_target("in_progress")=None` 对齐状态机 / c 放开 todo 回退(破坏闸门)。本批测试显式锁定此行为(防回归)。
- **⚪ 观察项 O2(low)**:测试数登记 +9 vs 实测 +10(差 1 来自既有测试重构,非问题)。
- **待修项回流 todo**: **CR-13-O1 产品决策点**(非代码 bug,回流 todo 作产品决策 a/b/c)。
### CR-260616-12 batch38 settings 域(F-01 agentic max 可配) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 15 件)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3(前端默认值硬编码 + clamp 回写闪现 + batch45 演进关系,均 low 非阻塞)
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 7 warning 全既存 dead_code;并发 batch45 改动共存编译通过)。注:审查中一次中间态 grep 抓到 batch45 改 mod.rs 时的 E0599,最终完整重跑 EXIT 0 确认 batch38 本身无错。
- **独立核验**(15 件):1:1 严格复刻 llm_concurrency 范式(AppState 字段 `agent_max_iterations: Arc<AtomicUsize>` + init 引用 `DEFAULT_MAX_AGENT_ITERATIONS=10`(agentic.rs:33)/ `run_agentic_loop(max_iterations)` 形参(agentic.rs:101)+ `0..max_iterations` 循环(agentic.rs:159)+ 达上限 warn+emit(agentic.rs:324-336)+ 3 调用点 ai_chat_send(commands.rs:103-106)/ai_regenerate(commands.rs:197-200)/try_continue_agent_loop(agentic.rs:477-487)load 快照透传 / `ai_set_agent_max_iterations` clamp 1-50 store(commands.rs:812-821)/ lib.rs:130 注册 / api/ai.ts:87-89 / Settings.vue input min1 max50(:275-277)+ watch 持久化 SQLite(:663-665)+ syncAgentMaxIterations Math clamp+debounce 300ms(:694-705)+ onMounted 同步防刷新(:766-775)/ onUnmounted 清 _agentIterTimer(:778-783)/ i18n 双语(settings.ts:79-80))。grep 确认无第 4 调用点遗漏。
- **⚪ 观察项**(均 low):
1. OBS1(前端默认值硬编码):Settings.vue:628/696 前端 `appSettings.get(..., 10)` 默认 10 与后端 const 巧合一致但非引用同一常量。范式层面观察(llm_concurrency 亦如此)。
2. OBS2(clamp 回写闪现):clamp 前瞬时值短暂显示后修正,视觉闪现功能无损。
3. OBS3(batch45 演进):lib.rs:111 注释 F-260616-03 标注 max_iterations 是 batch45 暂停态续跑前置基础,两批共存编译通过,演进链路清晰。
- **待修项回流 todo**: **无**
### CR-260616-11 batch39 ai chat 性能域(AR-8 节流评估 + B-53 snap 优化) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 6 件)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(增量缓存 quirk + deep watch 每帧检测,均 low 非本批)
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code;首跑增量缓存陈旧报 AiMaxRoundsReached not found E0599,清缓存重跑 EXIT 0 确认)。
- **独立核验**(6 件):
- **AR-8 前端滚动跟随已存在**(AiChat.vue:1110-1114 isNearBottom 80px + 234 onMessagesScroll + 412 showBackToBottom + 1139-1147 onContentChange)✅ — 完整存在,非本批新增。
- **AR-8 后端 50ms 合批未改**(stream_recv.rs diff 纯 B-42 error_type 映射,零 batch39 改动)✅ — 负 ROI 论证成立(rAF ARC-08 吸收重 parse + heartbeat 语义冲突 + flush 复杂度)。
- **B-53 snap 优化正确性**(AiChat.vue:1207-1210)✅ — 新 snap `{n: msgs.length, tc: msgs.map(m=>(m.toolCalls||[]).map(t=>({id,s})))}` 剔除冗余 id/role/len;body 逻辑(AiChat.vue:1218-1226)仅读 toolCalls 状态从不读被删字段 → 行为安全;流式 delta 不再触发 body 重算(toolCall 状态翻转/splice 淘汰仍触发)。
- **B-53 短路 + deep + MESSAGE_CAP 全保留**(AiChat.vue:1213 snap===lastMsgSnapshot return + 1233 deep:true + ai.ts:36/94-105 MESSAGE_CAP=200 length watch 独立)✅。
- **B-53 自包含**(lastMsgSnapshot 模块局部无外部消费 + collapseInactive 唯一调用点)✅。
- **注释清晰度**(AiChat.vue:1197-1202)✅ — 与代码完全吻合无误导。
- **⚪ 观察项**(均 low):
1. OBS1(缓存 quirk):增量缓存陈旧致首跑 E0599,清缓存重跑过。建议 CI 走 cargo check 不带增量。
2. OBS2(deep watch 每帧检测):B-53 仅短路 body,deep watch 本身仍每帧遍历 messages 含 content,极长流式消息 O(n) 成本。rAF 节流已吸收,非阻塞。
- **待修项回流 todo**: **无**(纯优化批次,零回归)。
### CR-260616-10 batch36 ai chat UX 域聚合(UX-02 消息操作栏 + UX-03 错误气泡操作入口) — ✅ 已审(PASS)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + 独立重跑双 check 佐证)
- **总体结论**: ✅ **PASS** — 🔴0 🟡2⚪3(ai_regenerate 并发竞态 med + commands.rs 时序倒置 med 非阻断 + 3 low)
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(vue-tsc 首跑报 useAiEvents.ts 缺 import 已补后过;agent 独立重跑双 check 佐证)。
- **独立核验**:
- **UX-02 操作栏**(AiChat.vue hover `.ai-msg-actions` 仅末条 复制+重新生成)✅。
- **ai_regenerate IPC**(commands.rs)✅ — generating 占用校验 + pop_last_assistant_round + save + spawn run_agentic_loop 复用 + active_conversation_id 一致性。
- **pop_last_assistant_round**(context.rs)✅ — 空 false / 末尾 user popped_any=false / 跨多轮 tool 三元组一并弹不留半截 / history_tokens saturating_sub。
- **UX-03 错误气泡**(useAiEvents.ts AiError push 附 errorType `as AiMessage` cast B-42 零改 + AiChat.vue 错误气泡重试复用 regenerate + canOpenSettings 判 auth|provider_config)✅。
- **只读消费 B-42**(ErrorType/AiErrorType/AiError 零改)✅。
- **🟡 观察项(待修项,细节见 agent transcript)**:
1. **CR-10-A(med 时序倒置)**:commands.rs:56/59 save 与 emit 时序。回流 todo。
2. **CR-10-B(med 竞态)**:ai_regenerate 并发竞态窗口。回流 todo。
- **⚪ 观察项**:代码块复制按钮(v-html 子元素无法绑事件)遗留单独立项;AI 气泡内嵌复制与新操作栏复制共存最小改动;context.rs 跨 crate(df-ai)改动非 batch35/37 锁。
- **待修项回流 todo**: **CR-10-A**(commands.rs:56/59 时序倒置 med)+ **CR-10-B**(ai_regenerate 竞态 med)。
### CR-260616-09 batch37 knowledge 域(F-260616-02 pending_review 归位·决策 a) — ✅ 已审(PASS)
- **commit**: 工作区未 commit(攒批)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + cargo test df-storage 双 EXIT 0 + grep/read 核验)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(注释遗留 + list_non_archived dead_code,均 low 非阻塞)
- **亲跑 `cargo check --workspace` + `cargo test -p df-storage`** ✅ — **双 EXIT 0**;df-storage 11 passed(含 `list_by_status_orders_by_confidence_semantics` / `list_by_status_filters_other_status` / `test_knowledge_cols_matches_record` 三个 knowledge 测试)。
- **独立核验**(5 件):
- **knowledge_list 默认收窄**(knowledge.rs:78-88)✅ — L86 `None => list_by_status("published")` 收窄纯 published;L84 `Some(s) => list_by_status(&s)` 显式透传(含 archived)向后兼容。
- **调用方零破坏** ✅ — grep list_non_archived src-tauri/ **零 Rust 调用方**;前端 src/api/knowledge.ts:15 + stores/knowledge.ts:61-66 loadList 默认 undefined→null→后端默认 published;Knowledge.vue:275/522 loadList() 无参走默认。**无调用方依赖旧「非 archived 全部」语义**。
- **merge_by_confidence 排序等价**(knowledge.rs:238-273)✅ — rank high=3/med=2/low=1/_=0 + created_at DESC,与 SQL(crud.rs:1363-1368 `CASE...DESC, created_at DESC`)权重+方向一致;双指针合并前 a/b 各 sort_by(cmp) 重排保证有序(L257-258);相等时 candidate 优先(L262)结果确定。
- **向后兼容** ✅ — published 仍在 library(默认)/ archived 显式可查 `knowledge_list(status='archived')` / candidate+pending_review 仍在 inbox(knowledge_list_candidates L226-231)。
- **i18n 文案**(zh/en knowledge.ts)✅ — inbox「待处理」/「Pending」+ 注释说明 F-260616-02 决策 a。
- **⚪ 观察项**:
1. **OBS1(low 注释遗留)**:Knowledge.vue:17/250 注释仍写「审核收件箱」,i18n 已改「待处理」;注释与新语义不符,建议清理(不影响功能)。
2. **OBS2(low dead_code)**:crud.rs:1478 `list_non_archived()` 收窄后全仓零调用方,成孤儿方法(编译器未报,宏展开 pub method 豁免);建议删除或 `#[allow(dead_code)]`
- **文档遗留(非代码,仅记录)**:df-knowledge 模块文档 L52/58/174-175 + 经验记录 L78 + df-storage 存储层文档 L114 多处 knowledge_list 默认语义/list_non_archived 描述过时,建议后续清理批次更新。
- **待修项回流 todo**: **无**(代码层面 PASS;两 low 观察项 + 文档遗留均非阻塞)。
### CR-260616-08 batch35 TaskDetail 域聚合(①-1 工作流推进按钮 + B-41 进度内嵌 + ①-3 i18n) — ✅ 已审(PASS)
- **commit**: 工作区未 commit(攒批)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验)
- **总体结论**: ✅ **PASS** — 🔴0 🟡1⚪1(blocked 态推进按钮语义偏差 med 非阻断 + lib.rs 混入非本批 low)
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — **双 EXIT 0**(4 warning 均既有 dead_code 非本批)。
- **独立核验**(11 件,grep + read 亲验):
- **workflow.rs 空dag选模板补丁**(L81-89)✅ — 三分支覆盖(nodes 非空→原 dag / 空+target Some→template_for 失败 Err「无对应工作流模板」 / 空+target None→原 build_dag);**与 CR-06 审的 ①-1 同代码,描述差异源于措辞非冲突**;dag shadowing 后 build_dag + dag_json 序列化落库实际拓扑(L92/L96)。
- **②-3/②-4 回调**(workflow.rs:232-233/267-305)✅ — cb_task_id/cb_target_status clone move;completed→Some(target)/failed→regression_target/任一 None→跳过;回调失败 warn 不回滚;advance_task_atomic 签名匹配(task_advance_node.rs:43-47)。
- **regression_target**(workflow.rs:44-51)✅ — testing→in_review/in_review→in_progress/in_progress→todo/其他→None。
- **TaskDetail.vue 推进按钮**(L71 v-if wfAdvanceAction / L260-267 computed / L77 disabled 互含 / L288-310 handleWorkflowAdvance 空 dag+taskId+target 调 run)✅。
- **B-41 进度内嵌 + exec_id 过滤**(L174-184 状态 / L313-314 过滤 / L411 onMounted + L419 onBeforeUnmount 生命周期对称)✅。
- **多 listen 无冲突**(workflow.ts:37-41 独立 unlisten / store 收全量 vs TaskDetail 按 exec_id 过滤,职责隔离)✅。
- **进度轻量近似**(L318-323 total 用已启动+已完成,前端无法预知后端模板节点数)✅。
- **template_for 对齐 ADVANCE_MAP**(三模板 in_progress/testing/done;in_progress 态 primary=in_review 无模板不显示)✅。
- **与手动 advance 并存**(L54-64 手动保留 / L66-91 工作流独立区块)+ 不碰 router/lib.rs listen ✅。
- **i18n 8 key 对称**(zh/en taskDetail.ts:41-49)✅。
- **🟡 观察项 O1(med,非阻断)**:**blocked 态推进按钮语义偏差** — blocked→in_progress primary(TaskDetail.vue:241-243)WF_SUPPORTED_TARGETS.has('in_progress')=true 显示按钮,但 template_for('in_progress') 是「todo→in_progress 从头执行」拓扑,blocked 恢复语义不该重跑 AiNode。UX 需确认(blocked 态应排除工作流推进或用恢复语义)。**非阻断**(后端回调逻辑正确,仅前端按钮显示语义)。
- **⚪ 观察项 O2(low)**:lib.rs +3 行(ai_regenerate L107 / list_tool_executions L127-128)非 batch35 范畴,属其他批混入工作区,不影响本批正确性。
- **待修项回流 todo**: **无**(均观察项非阻断;O1 blocked 语义建议主会话评估是否前端排除)。
### CR-260616-07 batch34 并行(B-42 错误码统一 + AE-08 审批历史面板) — ✅ 已审(PASS)
- **commit**: 工作区未 commit(攒批)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验源码)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(既存死代码 warning + B-42 前端消费未改留 batch36)
- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — **双 EXIT 0**(output L68/L69);cargo check Finished 48.33s(L67);6 warning 全既存死代码(df-ai finish_reason/call_type/SessionState/session_state + devflow risk_level/releases/node_executions never read),非本批引入。
- **独立核验**(B-42 + AE-08,grep 定位 + read 亲验源码):
- **B-42 ErrorType 枚举**(mod.rs:73-86)✅ — `#[serde(rename_all = "snake_case")]` + 5 variant(Auth/Network/Timeout/ProviderConfig/Unknown),每个文档注释说明分类标准;ProviderConfig→provider_config 与 types.ts:188 联合对齐。
- **B-42 AiError variant**(mod.rs:107-112)✅ — `error_type: Option<ErrorType>`,None 表示未分类(向后兼容旧 emit 点,L106 注释)。
- **B-42 8 emit 点映射**(agentic.rs×3 + stream_recv.rs×5)✅ — Auth(agentic:128)/ ProviderConfig(agentic:452)/ Unknown(agentic:333 + stream_recv:207)/ Timeout(stream_recv:163)/ Network(stream_recv:237/278)/ None(stream_recv:308 建连失败混合源难精确归类)。映射合理。
- **AE-08 list_recent SQL**(crud.rs:1246-1275)✅ — 专用 SELECT `ORDER BY requested_at DESC LIMIT ?1 OFFSET ?2`(L1259),绕过通用 query 宏(后者硬编码 ORDER BY created_at,本表无该列);limit.min(200) 钳制防滥用(L1253)。
- **AE-08 list_tool_executions IPC**(audit.rs:85-113)✅ — limit.unwrap_or(50)/offset.unwrap_or(0) → list_recent → DTO map;**敏感字段截断**(arguments_brief 120 / result_brief 160,L104-105),完整原值留库。
- **AE-08 truncate_chars**(audit.rs:71-78)✅ — `chars().count()<=max` 早返回 / 否则 `chars().take(max)` + push '…';**按 char 边界切,中文/emoji 安全**。
- **AE-08 lib.rs 全路径注册**(lib.rs:128)✅ — `commands::ai::audit::list_tool_executions`,避碰 mod.rs glob,零行为破坏。
- **AE-08 AuditLog.vue**(src/views/AuditLog.vue:1-311)✅ — 7 列表格 + 分页(prevPage/nextPage + hasMore=records.length===PAGE_SIZE + 空页回退 L105-108)+ 风险/状态/决策者标签映射;**类型内联**(ToolExecutionRecord interface L69-83,注释「不进 api/types.ts 避撞 B-42」)— 无 types.ts 交叉污染(关注点② ✅)。
- **B-42 前端消费**(关注点③)✅ — grep `error_type` src/ 仅 types.ts:204 定义,**零消费逻辑**(AiChat.vue 等不读 error_type);UX-03 留 batch36 符合任务约定。
- **⚪ 观察项**:
1. **既存死代码 warning**(cargo check 6 个,df-ai/devflow never read)— 非本批引入,既存 lint 级;建议后续清理(SessionState/risk_level 可能阶段2 未接入死代码)。
2. **B-42 前端消费未改**(关注点③已知)— error_type 字段已加但前端暂不读,UX-03 留 batch36;字段先落地不阻塞,合理。
- **待修项回流 todo**: **无**(B-42 + AE-08 全过,零缺陷;两观察项均已知/既存非回归)。
### CR-260616-06 batch33 阶段2 续(②-2/②-3/②-4 run_workflow task_id+回调 + ②-6 DAG模板) — ✅ 已审(PASS)
- **commit**: 工作区未 commit(攒批,与 batch32 + 决策记录 + todo/待审查 混合)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo test 全量无 tail + grep/read 核验源码)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(df-nodes 1 ignored + ②-3/②-4 回调无集成测试)
- **亲跑 `cargo test -p df-workflow -p df-nodes 2>&1`(无 tail 全量)** ✅ — **exit 0**;df-nodes **58 running → 57 passed; 0 failed; 1 ignored**(output L30/L90,比 CR-05 的 53 多 4 模板测试);df-workflow **23 passed; 0 failed**(L119);无 FAILED 无 error[。**去 tail 修正 CR-05 截断问题**。
- **独立核验**(四件 + ①-1,grep 定位 + read 亲验源码):
- **②-2 run_workflow 参数 + 前端透传**(workflow.rs:64-73 + api/workflow.ts:16-24 + stores/project/workflow.ts:23-26)✅ — 签名 `task_id: Option<String>, target_status: Option<String>`(L71-72)向后兼容;WorkflowRecord.task_id 填入(L104);前端 api `taskId?/targetStatus?`(L16-17)snake_case 透传(L23-24);stores runWorkflow 透传(L26)。旧调用方不传→None 零行为破坏。
- **②-3 完成回调**(workflow.rs:267-273)✅ — `match (cb_task_id.as_ref(), cb_target_status.as_deref())` 都 Some → completed 分支 `advance_target=Some(target)`(L270)→ advance_task_atomic 推进到 target_status。
- **②-4 失败回调退回**(workflow.rs:44-51 + 271)✅ — failed 分支 `advance_target=regression_target(target)`;映射 testing→in_review / in_review→in_progress / in_progress→todo / _(done/blocked/cancelled)→None;in_progress→todo 回起点符合「执行未达预期重排」直觉(L42-43 注释);起点 todo 无可退态→None。
- **回调解耦语义**(workflow.rs:274-294)✅ — advance_task_atomic Err 仅 `tracing::warn!` 不回滚(工作流 status 已写库 L251,回调失败不撤销);`TaskRepo::new(&db)`(L275)复用 db Arc(clone 廉价)。
- **②-6 DAG 模板**(task_workflow_templates.rs:1-152)✅ — `template_for(target)` match in_progress/testing/done→Some,else None(L28-35)不 panic;三模板拓扑正确(in_progress 单 ai / testing ai→human 串行 / done 单 human);testing+done 模板 human options 含「拒绝」(L67/L85)触发 ②-5 reject→Err→failed→②-4 退回闭环;config 留空由 run_workflow 全局注入+deep_merge(衔接 ④-1 L14-17);4 测试断言拓扑(unknown_none/single_ai/ai_to_human_serial 含「拒绝」断言 L141/single_human)。
- **①-1 DagDef 来源选模板**(workflow.rs:81-89)✅ — dag.nodes 空+target Some→`template_for`(L83).ok_or_else(Err「无对应工作流模板」)(L84);空+None→原路径 build_dag 校验(L85);非空→用传入 dag(L88)。三分支零行为破坏。
- **⚪ 观察项**:
1. df-nodes **1 ignored**(L90,running 58 / passed 57 / ignored 1)— 登记关注点提「57 passed」未提 ignored;ignored 非 failed,建议确认哪个测试 `#[ignore]`(可能慢/集成测试留阶段2收尾)。
2. **②-3/②-4 回调无集成测试**(登记关注点②已知)— 已亲核 diff 逻辑正确(match 分支 + 解耦语义),但缺自动化回归保护;回调失败/退回路径仅靠代码审查覆盖。建议阶段2 收尾补集成测试(workflow 跑通→advance 验证)。
- **待修项回流 todo**: **无**(四件+①-1 全过,零缺陷;两观察项均已知/非回归)。
### CR-260616-05 batch32 阶段2 首批(④-1 config 下沉 + ②-1 注册 TaskAdvanceNode + ②-5 HumanNode reject) — ✅ 已审(PASS)
- **commit**: 工作区未 commit(攒批,与 CR-03/04 + 决策记录 + todo/待审查 混合)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo test + grep/read 核验源码,不信 agent 自报"53 passed")
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪1(df-nodes 测试数字 tail 截断未单独显示,exit 0 佐证通过)
- **亲跑 `timeout 240 cargo test -p df-nodes -p df-workflow`** ✅ — **exit 0**;df-workflow **23 passed**(deep_merge_tests 6 + executor `node_config_overrides_global_in_node_context` 1 + cancelled 3 + state/conditions 13);df-nodes human_node reject 测试 exit 0 保证通过(`tail -40` 截断未单独显示数字,exit code 0 佐证)。**不信 agent 自报「53 passed」**。
- **独立核验**(三件,grep 定位 + read 亲验源码):
- **④-1 deep_merge config 下沉**(dag.rs:169 + executor.rs:105 + registry.rs:52)✅ —
- `deep_merge(global,node)`(dag.rs:169-188):两端 Object 递归合并(同 key 节点级覆盖 L177-181 / 新 key 保留 L179);非两端 Object 节点级直接覆盖 L186(含 Null 显式空);**Null 语义=显式置空覆盖(非跳过),跳过=不写 node_configs key**(L164-165 注释)符合登记关注点。
- `NodeContext.config`(executor.rs:105-108):`match node_configs.get(id) → Some(deep_merge(global,node_cfg)) / None(initial_config.clone())`;**缺失 key 走 None 分支 == 旧 initial_config.clone() 零行为破坏**。
- `build_dag`(registry.rs:52):`add_node_with_config(id,node,node_def.config.clone())` 下沉 NodeDef.config。
- **5 维度全测过**:同 key 胜(`node_overrides_global_scalar`)/ 新 key 留(`node_adds_new_key`)/ 嵌套递归(`nested_object_recursive_merge`)/ 非 Object 覆盖(`array_replaces_not_concat`)/ 空 no-op(`empty_node_config_is_noop`+`missing_node_config_treated_as_noop`)。
- **零行为破坏**:HumanNode/AiNode/TaskAdvanceNode 节点 NodeDef.config 空/缺失 → node_configs 无 key → None 分支 == 旧行为。
- **②-1 TaskAdvanceNode 注册**(state.rs:235-250)✅ — `build_registry(db: Arc<Database>)` 改签名;`register("task_advance", move |_| Box::new(TaskAdvanceNode::new(db.clone())))`(state.rs:248-250)move 闭包捕获 Arc<Database>,clone 满足 Send+Sync+'static 满足 NodeFactory 签名;init `build_registry(db.clone())`(state.rs:194)db move 前 clone 避 E0382。D-03 走 df-nodes Node trait 对齐。
- **②-5 HumanNode reject 语义化**(human_node.rs:19-132)✅ —
- `REJECT_KEYWORDS`(L19-22):中「拒绝/驳回/退回/否决」+ 英「reject/decline/declined/deny/denied/no/block」。
- `is_reject_decision`(L25-28):trim+to_lowercase 归一化 + **整词精确匹配(== 非 contains)** → 防「拒绝啦」误伤(测 L846 `!is_reject_decision("拒绝啦")` ✅)。
- `contains_reject`(L31-33):picked 任一命中即拒(多选场景)。
- execute(L120-132):`count_ok && each_valid``contains_reject` → Err「人工审批被拒绝(用户选择: X);意见: Y」。
- **边界全过**:整词防误伤(L846)/ 大小写空白归一(L836-838)/ 多选任一即拒(L848)/ options 空自由文本仅拒绝词 Err 其余 Ok(L13-15 注释)/ 行为变更 Ok→Err executor set_failed → 工作流 failed(L113-119 注释)。
- **⚪ 观察项**:亲跑命令带 `tail -40` 截断 df-nodes 测试数字(仅见 df-workflow 23 passed);**exit code 0 佐证 df-nodes+df-workflow 全过**,非缺陷。后续审查亲跑命令去 tail 或用 `2>&1 | tee` 保全量。
- **待修项回流 todo**: **无**(三件全过,零缺陷)。
### CR-260616-03 batch30 ①类修复批次 — ✅ 已审(PASS)
- **commit**: 工作区未 commit(攒批,与 CR-04 混合;CR-03 范围文件干净,无阶段2混入)
- **审查时间**: 2026-06-16(独立审查 agent · 亲核 diff + grep 核验,不信主代理"46 passed"声明)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪1(set_cancelled TOCTOU 低频,条目已知)
- **独立核验**(CR-03 登记范围 8 文件,范围干净):
- **CR-01-D 错误区分**(task_advance_node.rs)✅ — 同态(from==to)→`Validation("相同状态,无需推进")`;非法转换→`InvalidState{current:"X→Y(非法状态转换)"}`;NotFound(任务不存在)。三类拒绝错误类型+消息区分,前端 e.toString() 可分辨。3 测试 assert 含 from→to 上下文(illegal_skip/terminal_done/same_status)。
- **CR-03-b openai source 链** ✅ — `Error::from(e).context("SSE 流错误: {e}")`,Display=ctx 逐字节等价旧 `anyhow!`,source 链保留。同 CR-04 anthropic 模式。
- **CR-03-a resolve_project_label 三臂**(audit.rs)✅ — Ok(Some)→项目名 / Ok(None)→"项目已不存在" / Err→裸 id+warn(DB 故障不误报"已不存在"误导用户)。
- **CR-03-c ToolCard find**(ToolCard.vue)✅ — computed Map(O(N)建表)→function find(O(N)提前退出);2 调用点 `.value[id]``(id)` 改对。
- **CR-01-G i18n 分级**(TaskDetail.vue + i18n×2)✅ — loadFailed/advanceFailed 加 `{msg}` 插值;catch 走 `t(key,{msg:e.toString() ?? common.unknownError})`;common.unknownError zh/en 均有(common.ts:16)。**关注点② 前端兜底可显示区分消息** ✅。
- **复核-新④ 终态守卫**(workflow.rs cancel_workflow_node)✅ — get→match:Pending/Running/Waiting→set_cancelled+Ok;终态→Err"已终态,无法取消"。行为变更正确(避免静默覆盖终态)。
- **⚪ 观察项(关注点①,条目已知"低频忽略")**:`set_cancelled`(state.rs:95-100)**无终态守卫**,直接 `insert(Cancelled)`。IPC 层 get→match→set_cancelled 两步非原子:理论 TOCTOU 窗口(get 时 Pending 通过守卫,set 前被 executor 并发改终态)→ set_cancelled 强制覆盖终态(如 Completed→Cancelled 语义错位)。**实际低频**(人工取消+executor 竞争窗口极小)+ 最坏仅终态语义错位不崩溃 → med 观察项,IPC 守卫是主防线。彻底修需 set_cancelled 加守卫或 CAS,低频场景成本不值。
- **待修项回流 todo**: 无明确缺陷;TOCTOU 属条目已知低频观察,非 CR-03 引入回归。
### CR-260616-04 batch31 NodeCancelled 事件语义+anthropic source链 — ✅ 已审(PASS)
- **commit**: 工作区未 commit(攒批,与 CR-03 + 阶段2 混合)
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 test + grep 核验,不信主代理"16 passed"声明)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪1(前端消费缺失,既存非回归)+ ⚠ 范围混淆 flag
- **独立核验**(CR-04 登记范围 4 文件):
- **events.rs NodeCancelled variant** ✅ — `NodeCancelled { node_id }`(events.rs:64)新增,语义注释清晰(取消=用户主动 vs NodeFailed=失败)。
- **executor.rs is_cancelled 二分** ✅ — L148-166:取消节点→emit NodeCancelled(不 set_failed,Cancelled 已终态避免非法转换崩溃);失败节点→set_failed+NodeFailed。零行为破坏(工作流仍 Err 中止,first_err 逻辑保留)。
- **types.ts node_cancelled** ✅ — L128 手对齐后端 serde(rename_all snake_case)。
- **anthropic_compat.rs:533 Display 不变** ✅ — `Error::from(e).context(format!("Anthropic SSE 错误: {}", e))`,Display = context = "Anthropic SSE 错误: {e}" 逐字节等价旧 `anyhow!`;e 进 `.source()` 链可追溯。
- **亲跑 `cargo test -p df-workflow`** ✅ — **23 passed**(主代理称 16,实际 23 含 deep_merge/node_config 新测试),关键 `test_cancelled_node_emits_node_cancelled_event` ✅ 验证取消节点 emit NodeCancelled 非 NodeFailed。
- **claim③ 无穷举 match** ✅ — 亲 grep:后端无 `match WorkflowEvent` 穷尽(全 matches!/构造点),前端 src/ node_failed/node_cancelled **仅 types.ts 定义无消费**
- **⚠ 范围混淆 flag**(重要):executor.rs 工作区 diff 混入**阶段2 config 下沉**(L99-108 deep_merge 全局+节点 config + EchoConfigNode 测试 + node_config_overrides 测试),**非 CR-04 范围**(未登记待审查)。该部分 test 过但不属本批结论覆盖。阶段2 改动(`dag.rs`+102 / `human_node.rs`+46 / `state.rs`+21 / executor.rs config 部分)需主会话另立 CR-05 登记后审。
- **⚪ 观察项(既存非回归,建议评估)**:NodeCancelled 事件**前端无消费逻辑**(src/ 仅 types.ts 类型定义,无 switch/render 处理)。取消节点用户暂无"已取消"区分 UI 反馈。**非 CR-04 引入**(CR-04 前 node_failed 前端亦不处理,属既存渐进式类型对齐),NodeCancelled 用户价值依赖后续补前端事件处理。
- **待修项回流 todo**: 无明确代码缺陷;前端事件消费缺失属既存功能不完整,建议主会话评估是否立项(非 CR-04 待修项)。
### CR-260616-02 batch29+R-PD-10 代码改动批次 — ✅ 已审(PASS)
- **commit**: `fc767e1` 优化: R-PD-10 storage_err DRY统一 + FR-D3 migrations if链转数组循环
- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 test 核验,不信主代理"全过"声明)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **独立核验**:
- **亲跑 `cargo test -p df-storage`** ✅ — crud 单元 **22** passed + project_soft_delete 集成 **11** passed,0 failed(覆盖 storage_err 替换后 CRUD / advance_status_atomic CAS / soft_delete / update_field 白名单 / migrations run() 全量路径)。主代理"11集成全过"声明属实,额外验证 22 单元。
- **R-PD-10 storage_err DRY** ✅ — helper `fn storage_err<E: ToString>(e: E) -> Error { Error::Storage(e.to_string()) }`(crud.rs:19)泛型等价旧闭包;残留旧模式 grep **=0**(无遗漏);test 全过证 110 处替换无语义偏差。
- **FR-D3 migrations if→数组循环** ✅ — `steps: [(i32, fn(&Connection)->Result<()>); 15]`(migrations.rs:43)+ `for (version, migrate_fn) in steps { if current_version < version { migrate_fn(conn)?; } }`,与原 15 个 `if current_version<N { migrate_vN(conn)?; }` **逐字等价**(顺序[1..15]+条件`<version`+`?`传播)。零行为变更属实。
- **migrate_vN 签名匹配** ✅ — migrate_v1~v15 全 `fn(&Connection) -> Result<()>`,与 steps 数组 fn 指针类型精确匹配。
- **迁移数据安全三层保留** ✅ — ①版本门控 run() 开头 `MAX(version)`(migrations.rs:29)②各 migrate_vN 末尾 `INSERT INTO schema_version`(L67/75/.../274)③column_exists PRAGMA 幂等探测(L7-9,v4-v15 各处)。
- **待修项回流 todo**: **无**(全过,无新缺陷)。
### CR-260616-01-fixes 审查待修项修复批次 — ✅ 已审(PASS)
- **commit**: `4dc5219` 修复: CR-01审查待修项①类7项(CSS补 testing/blocked 徽章 + CAS防回收站推进 + DRY + 注释 + 测试改名 + advancing 接文案)
- **审查时间**: 2026-06-16(独立审查 agent · 防"主代理已核查双绿"代入,亲核源码)
- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(范围外观察项)
- **独立核验**(不信主代理核查声明,grep/read 亲验 7 项①类修复):
-**CR-01-B CAS deleted_at 语义** ✅ — `soft_delete`(crud.rs:800)设 `deleted_at` 非空;`advance_status_atomic` 两分支(bump_rounds + 普通,crud.rs:855/859)均加 `AND deleted_at IS NULL`,回收站任务 `affected=0 → None` 必败。D-02 软删收口正确,一处关闭。
-**CR-01-A CSS 配色跨视图一致** ✅ — TaskDetail.vue:344-345 与 Tasks.vue:413-414 **逐字镜像**(testing 橙 `#ff9800` / blocked 红danger 0.5px 边框),跨视图统一。
-**CR-01-C DRY 文案零变更** ✅ — `ALL_STATES`(task_state_machine.rs:48-56)= `["todo","in_progress","in_review","testing","done","blocked","cancelled"]`,`join("/")` = 原硬编码串,顺序与值全等,无运行时文案变化。
- **CR-01-H 测试改名** ✅ — `cas_returns_none_when_status_mismatch`(task_advance_node.rs:313)真存在,改名后注释指向正确的 CAS 失败测试。
- **CR-01-I advancing i18n** ✅ — zh/en 均有(taskDetail.ts:38 `推进中.../Advancing...`),TaskDetail.vue:63 接 advancing 态。
- **CR-01-F 白名单注释** ✅ — `allowed_columns_for("tasks")`(crud.rs:331)数组无 `review_rounds` + 注释说明专用路径(仅 advance_status_atomic),收口防误补。
- **CR-01-E state.rs 注释** ✅ — build_registry(state.rs:240)注释准确,说明 TaskAdvanceNode 未注册原因(阶段2 DAG 联动时加)。
- **范围外(记录不审,非本批缺陷)**:
-**工作区 crud.rs 226行未提交** = `R-PD-10` `storage_err` DRY 重构(提取包装函数替换散落 `.map_err(|e| Error::Storage(e.to_string()))`),非 4dc5219 范围,独立审查留待其 commit。
-**`status-abandoned` CSS class ↔ `cancelled` 后端态** = 历史命名别名(`constants/project.ts:83` `cancelled: 'status-abandoned'` 映射闭环,功能正确),非本批引入。命名语义瑕疵(abandoned 弃用 ≠ cancelled 取消),low,可选优化。
- **待修项回流 todo**: **无**(7 项全过,无新缺陷)。
### CR-260616-01 任务推进链(batch28) — ✅ 已审
- **commit**: `d2cb38c` 新增: 任务推进链(7态状态机+advance_task CAS原子写)+软删除+前后端7态对齐
- **审查时间**: 2026-06-16(5 路并行多角度·防代入独立核验)
- **决策依据**: D-260616-01(前端7态对齐) / D-02(任务软删除) / D-03(advance_task 走 df-nodes Node trait) / D-04(阶段1先行)
- **总体结论**: 🔴0 🟡6 ⚪4 — **质量优**
- **8 点 checklist 全过**:
- 状态机矩阵 ✅(7态15转换,前后端单一真相源)
- CAS 原子 ✅(`WHERE id AND status=expected` + affected=0 报错)
- 退回累加原子 ✅(同 UPDATE)
- V15 迁移幂等 ✅(版本门控 + column_exists 双层,八版零事故)
- IPC 真 thin ✅(D-03 守住,task.rs 仅 3 行转发)
- i18n 成对零遗漏 ✅(28 key)
- create_task 不暴露 rounds ✅
- 测试 25 个 ✅
- **F-03 status 双写 = 已知延后**(阶段3 三件同落地,非新缺陷)
- **范围外澄清**: df-ai/lib.rs(`mod retry`) + script_node.rs(`shell_type`) = 同 commit 无关连带改,零风险误报
- **待修项回流 todo**(CR-01-A~I,见 [todo.md](./todo.md) "审查发现待修项"区):
- [P1] **CR-01-A** TaskDetail.vue scoped style 补 `.status-testing`/`.status-blocked`(D-01 前端7态遗漏,testing/blocked 徽章裸渲染丢样式;同查 Tasks.vue/Dashboard)
- [P1] **CR-01-B** crud.rs advance_status_atomic CAS SQL 加 `AND deleted_at IS NULL`(get_by_id 宏无软删过滤→回收站任务可被 advance,D-02 软删对齐遗漏,语义错乱)
- [P2] **CR-01-C** task_advance_node.rs:46 错误7态硬编码改 `ALL_STATES.join("/")`(DRY 单一事实源)
- [P2] **CR-01-D** task_advance_node.rs:62 同态拒绝(from==to)/非法转换区分(现合并 InvalidState,前端无法分辨)
- [P2] **CR-01-E** state.rs build_registry 注释补「task_advance 未注册(阶段2联动时加)」
- [P2] **CR-01-F** crud.rs:325 白名单注释补 review_rounds 专用路径
- [P3] **CR-01-G** TaskDetail.vue:218 advance 失败错误走 i18n 分级(en locale 也中文)
- [P3] **CR-01-H** task_advance_node.rs:297 测试改名(实际未测 CAS 失败)
- [P3] **CR-01-I** i18n `taskDetail.advancing` 冗余 key 处理