Files
DevFlow/docs/待审查.md

228 KiB
Raw Blame History

待审查队列

职责分离:

  • 主会话(编码合并后 / 巡检)→ 【写入】"当前队列"区,登记待审查项(范围 + 维度 + commit)。
  • 审查 agent(独立会话 / workflow)→ 【读取】"当前队列",执行审查,完成回填结论 + 移到"已审归档"。

这里只放审查登记,不放推进待办(推进待办见 todo.md)。 主会话不在这里做审查,只登记 + 读 todo 推进。审查发现的【待修代码项】回流 todo 作推进待办。

审查规则(审查 agent 遵守)

  • 只读审查,不改代码
  • 独立 grep/read 核验源码,不信文档 / 会话描述 / 设计声明的结论(防上下文污染)。
  • 每条 finding 须 file:line 源码佐证 + evidence。
  • 严重度:high(破坏性 / 数据错 / 回归) / med(逻辑瑕疵 / 边界漏) / low(风格 / 可读)。
  • 完成回填:总体 verdict(PASS / ISSUES / WATCH)+ high/med/low 清单 + 待修项回流 todo。

状态图例

🟡 待审 / 🔵 审查中 / 已审(结论) / 🔴 待修(有 high 问题)


当前队列

CR-260622-01 AI Chat 真实会话实测分析(84dc36c8·prompt 1.32M/completion 7.5K·3P0+3P1+2P2)— 已审(ISSUES·低优残留2项·P0清零)

  • 范围:实测数据反推(非静态代码审查)。从生产 DB 会话 84dc36c8(D:\Downloads\kms,整理三知识库 HTML)全量 86 条消息反推上下文/工具/agent 行为缺陷。报告 docs/05-代码审查/aichat-会话实测分析-2026-06-22.md,分析脚本 .temp/analyze_session.py,DB 只读 %APPDATA%/top.1216.devflow/devflow-dev.db
  • 维度:(a) 独立核验 db 实测数据(token 比/重复 tool 结果/system 污染/时序);(b) 核验源码根因(报告 finding 均为"假设"待证)。
  • 核验方式:只读 DB(mode=ro)+ grep/read 源码,不信报告结论(防上下文污染,code-review-anti-contamination)。
  • 待审点(8):
    • P0-1 压缩标记 compressed 未真降 prompt(1.32M)— 核 compress 模块是否替换为摘要还是仅打标记
    • P0-2 patch_file/read_file 部分路径裸吐文件全文(raw 6204c)非 JSON — 核 tool_registry.rs 返回序列化分支
    • P0-3 重复 tool 结果(seq 11-14 四份/69-71/83-85 三份同长度同秒)— 核 ai_messages 落库去重 / 流式 patch 合并
    • P1-1 过早宣称完成 3 次被推翻,无 checklist 自检 — agent 行为层
    • P1-2 末尾 #82→#85 tool 全返回后无 assistant 回复(中断)— 核 generating 复位/try_continue
    • P1-3 三独立文件(mysql/redis/linux)整理本可并行却严格串行,无 fan-out 调度 — 印证 aichat-b-route-parallel-multiround
    • P2-1 seq 0 system 消息内容污染 + 时序错乱(晚 seq 1 十小时)— 核 system 注入 + timestamp
    • P2-2 同文件反复 read(mysql/linux 各 4×)无缓存
  • 最高杠杆:P0-2(工具返回改 diff/changed 而非全文)→ prompt 降一个量级。
  • 关联:aichat-techdebt-audit-2026-06-21 devflow-generating-statemachine aichat-b-route-parallel-multiround aichat-decision-capability

复审结论(2026-06-24·3 agent 独立 grep/read 核验当前源码形态,防上下文污染): ISSUES(低优残留,P0 清零可归档)

  • P0-1/P0-2/P0-3 已销账(报告 06-23/06-24 修正段:压缩 is_active 白名单隐式修复 / truncate 破坏 JSON 修 / 重复 tool_result 实测 tool_call_id 全不同证伪)
  • P1-2 末尾中断 🟡缓解:guard.rs GeneratingGuard RAII(正常 reset + Drop 兜底)+ ConvState 5态 enum 状态机已系统性覆盖"generating 散布复位 + 无 panic 兜底"原根因;报告 06-23 修正推翻原假设(84dc36c8 seq85 后疑 max_iterations 收敛/自然停,非 generating 卡死)。归 BUG-260623-03 审批 pending 无超时(待决策,推荐前端倒计时)。
  • P2-1 system 时序 🟡部分缓解:DB created_at 全消息统一 conv_created_at 不乱;但 ChatMessage.timestamp(provider.rs:44 system() 用插入时刻)system 摘要打戳不可靠 → 前端排序应弃 timestamp 改 seq/created_at。role 污染数据级偶发(压缩摘要 LLM 输出含 assistant 语义,非结构 bug)。
  • P2-2 非代码文件缓存 🔴仍存:read_symbol(7c2e3b2/7f6aa1e)治 .rs/.ts/.vue 代码文件(降 24.4x);HTML/markdown/JSON 等非代码文件无 session 级缓存,LLM 仍 read_file 全文回灌。

待修项回流 todo:🟡 2 项低优(CR-260622-01-P2-1 前端排序弃 timestamp / CR-260622-01-P2-2 非代码文件 session 缓存,归 B 路线 prompt 策略或工具侧缓存)。


CR-260620-04 Agent 最大轮次支持「不限」(working tree 未提交:0=不限 约定·config.rs clamp 0-50 + mod.rs effective_max 0→usize::MAX + GeneralPanel checkbox + App.vue clamp + i18n labelUnlimited) — 已审(PASS·2 WATCH·巡检 2026-06-20 独立 grep/read 核验控制流)

  • 范围:新功能。约定 0=不限:后端 config.rs:69 clamp(1,50)→(0,50);mod.rs:542 loop 上界算 effective_max(0→usize::MAX,for 到不了上界,靠 stop_flag/收敛/审批退出,达上限暂停 AiMaxRoundsReached 不触发);前端 GeneralPanel 加「不限」checkbox(绑 agentMaxIterations===0)+ input :disabled + clamp 支持 0 + computed unlimited;App.vue:107 clamp 跟进(iter===0?0:...);i18n zh/en desc 加 0=不限 + labelUnlimited。
  • 维度:正确性(effective_max 语义/达上限暂停分支 1172 在 0 模式是否漏退出)/ 边界(0 全链路 config→store→load→loop 透传)/ 回归(设 50 仍触发暂停?默认 10 不变)/ UI(checkbox 切换+持久化+disabled)/ 一致性(前端 0=不限 vs 后端 0→usize::MAX 对齐)
  • 核验方式:git diff working tree 5 文件逐行 + cargo check EXIT 0 + vue-tsc EXIT 0(主代已跑)
  • 待审点:effective_max 计算位置(542 for 前)/ usize::MAX 作 for 上界无溢出/意外 / unlimited computed set 与 syncAgentMaxIterations 协同 / App.vue 三元与 GeneralPanel clamp 语义一致 / 0 模式 1172 暂停分支确实不触发 / 不限模式无限轮风险仅靠 stop_flag/收敛/审批兜底(无软上限)

复审结论(2026-06-20·巡检独立 grep/read 核验控制流,不信"主代已跑"声明): PASS🔴0 🟡0 2 WATCH 1

① 核心控制流 0 模式暂停分支不误触发 PASS(深度核验,主会话待审点):

  • mod.rs:543 effective_max = if max_iterations==0 { usize::MAX } else { max_iterations }
  • mod.rs:544 for iteration in start_iteration..effective_max — 0 模式上界 MAX,for 仅能经 return/break 退出,不可能跑满上界
  • for 退出仅 3 路径:stop_flag→return(:559)/ 审批等待→return(:1162)/ 收敛→break(:1143 if !has_tool_calls { converged=true; break; })
  • mod.rs:1175 if !converged(for 结束后)— 0 模式 for 仅经 converged break 到达此处(return 路径直接返回不经此),故 !converged=false,跳过 AiMaxRoundsReached 暂停分支
  • 注释 :1174 "max_iterations=0 时 effective_max=usize::MAX,for 不会正常结束至此,故不触发暂停" 准确

② usize::MAX for 上界无溢出 PASS:

  • Rust Range 0..usize::MAX iterator 到 MAX-1 后 next 返回 None(标准库保证不溢出)
  • for 体内 iteration 仅作计数,无 iteration+1 致 MAX 溢出运算;实际 loop 靠 break/return 退出,不会真迭代至 MAX-1

③ 0 全链路透传一致性 PASS:

  • 前端 GeneralPanel unlimited→agentMaxIterations=0 → IPC → config.rs:69 clamp(0,50) store 0 → chat.rs:167 loadrun_agentic_loop max_iterations=0effective_max=MAX
  • App.vue:107 iter===0 ? 0 : Math.min(50,Math.max(1, iter??10)) 与 GeneralPanel syncAgentMaxIterations(!==0 透传)语义一致
  • GeneralPanel unlimited computed(get ===0 / set 0↔10)+ input :disabled="unlimited" + 持久化(appSettings SQLite,App.vue onMounted 读 0 透传后端)

④ 回归 PASS:

  • 默认 DEFAULT_MAX_AGENT_ITERATIONS=10(state.rs:556)不变,clamp 0-50 不影响默认
  • 设 50:effective_max=50,跑满未 break → :1175 !converged → 暂停触发(原行为保留)
  • 设 0:如 ① 不触发暂停

⑤ i18n 中英对称 PASS:en/zh descAgentMaxIterations 加 "0=不限" + labelUnlimited 新 key 中英对称


LOW-1:config.rs:60-61 docstring stale — 仍写"范围双 clamp(command 端 1-50 + 前端 input min/max)",实际 :67-69 已改 clamp 0-50。文档与代码不符,建议同步。

LOW-2:GeneralPanel input min="1" max="50" 未改。unlimited 勾选时 :disabled 不冲突;用户不勾选直接手输 0 → v-model 允许 + sync !==0 透传(=不限语义自洽)。min=1 与"可手输 0"轻微矛盾,有 unlimited 复选框为主路径,影响低。

WATCH-1:0 模式(不限)无软上限 — LLM 持续调工具不收敛时理论无限轮烧 token,仅靠 stop_flag/收敛/审批退出。用户主动选"不限"即接受此风险(设计取舍,非 bug)。建议 UI desc 补风险提示(当前 descAgentMaxIterations 仅"0=不限",未提烧 token 风险)。

  • 待修项回流 todo: 🔴/🟡

CR-260620-01 AI 授权目录改进批(working tree 未提交:strip_verbatim比对侧收口+黑名单统一+search_files symlink+bind reload+toast接线+单测·state.rs/tool_registry.rs/mod.rs/project.rs/chat.rs/AllowedDirsPanel.vue/Settings.vue/i18n×2) — 已审(ISSUES·🟡12)

  • 范围:三方审查(安全/UX/跨端)交叉印证改进。误弹窗核心根因(strip_verbatim 仅写入侧,比对侧遗漏)收口到 is_authorized 单点(candidate strip)。黑名单两套不一致(validate_path contains vs is_in_system_blacklist 分段)统一单一来源 + 补 windows 根/programdata/.ssh。search_files symlink 逃逸(file_type 不跟随)。bind 路径 reload(project.rs create_with_binding/update_project/relocate 三处)。AllowedDirsPanel toast 接线 + pickDir 即加。P0 workspace_root 收紧已回退(编译期写死不适合分发,转待决策)。
  • 维度:安全(授权绕过/黑名单/symlink 逃逸)+ 一致性(读写形态/黑名单统一)+ 回归(现有测试 + 新增单测)
  • 核验方式:git diff working tree 逐文件核验 + cargo test state:: 18 passed + vue-tsc EXIT 0
  • 待审点:is_authorized strip 收口是否覆盖所有调用方(resolve :334 canon_resolved / check_path :448)/ validate_path 统一后 .ssh 等是否仍挡 / 黑名单 windows 段命中即拒是否误伤合法路径 / bind reload 是否遗漏 path 写入点

复审结论(2026-06-20·主代独立 grep/read 核验源码形态 + cargo test state:: 18 passed): ISSUES🔴0 🟡1 2

范围边界声明:git diff HEAD --stat 显示本批实际改动 13 文件,其中 AiChat.vue(confirmNewConversation 去中断弹窗 + 删 isViewingGenerating dead code)+ useAiConversations.ts(newConversation 去 generatingConvs.clear)属 F-09 决策e 真并发改动,非授权目录范围,已由独立 CR-260620-02 登记(已审 PASS·2),本审查不覆盖(避免审查污染/越界)。Cargo.toml 仅 LF/CRLF 行尾(warning 无实质改动)。本审查严格聚焦授权目录 8 文件。


① is_authorized strip 收口覆盖性 PASS(核验 4 调用方):

  • state.rs:356-365 is_authorized 比对侧 strip_verbatim(candidate.to_path_buf()) 单点收口
  • tool_registry.rs:326 is_authorized(&resolved) — resolved 词法锚定(PathBuf::from(path)root.join(path)),无 verbatim 前缀,strip 无害(原样返回),逻辑等价
  • tool_registry.rs:334 is_authorized(&canon_resolved) — canon_resolved 经 std::fs::canonicalize 返回带 \\?\ 前缀,strip 后命中 persistent(写入侧也 strip),形态一致 (误弹窗核心根因修复点)
  • state.rs:460 check_path_authorization is_authorized(&resolved) — resolved 词法层无前缀,strip 无害等价
  • audit/mod.rs:187 check_path_authorization 转调,同源 strip 生效
  • 覆盖完整:4 个 is_authorized 调用点 + 1 个 check_path_authorization 全部经比对侧 strip,无遗漏调用方。

② validate_path 凭据防护 PASS(统一后仍挡):

  • tool_registry.rs:136-138 .ssh/.aws/.gnupg + Windows/ProgramFiles/ProgramData 统一走 crate::state::is_in_system_blacklist(分段匹配,消除 contains 子串误伤如 my\windows\backup)
  • tool_registry.rs:140-142 AppData 保留 lower.contains("\\appdata\\") 单独挡(用户级数据,分段匹配会误伤 D:\backup\appdata 合法目录名 — 决策合理,已注释说明)
  • .ssh/.aws/.gnupg 迁移后 is_in_system_blacklist:422 任意路径段命中即拒,validate_path 经 PathBuf::from(&normalized) 传入分段匹配,等价且更精准
  • 纵深防御完整:validate_path(词法前置)+ is_authorized 内黑名单(运行时双保险)+ set_allowed_dirs:652 预校验(防持久化系统目录),三层防线齐全。

③ 黑名单 windows 段命中即拒误伤面 PASS(分段匹配无子串误伤):

  • state.rs:408 if *seg == "windows" { return true } — seg 是按 //\ 分隔的完整路径段,E:\my-windows\backup 分段为 ["e:","my-windows","backup"],"my-windows" != "windows" 不误伤
  • test_blacklist_no_false_positive:892 印证(E:/my program files backup/x 不拒)
  • test_blacklist_windows_root_programdata_creds:983 印证 win.ini/explorer.exe/ProgramData/.ssh/.aws/.gnupg 全拒 + devflow 自身 + D:\backup\appdata 不误伤
  • Windows 根段命中即拒(含根本身,不再限定 system32 子目录):防 win.ini/hosts 等敏感文件,合理增强
  • .ssh/.aws/.gnupg 段在 cfg!(windows) 块外(跨平台):设计正确,Unix 凭据目录同样需挡

④ search_files_recursive symlink 逃逸 PASS:

  • tool_registry.rs:1797-1804entry.file_type().await?(不跟随 symlink)+ symlink 一律 continue(不匹配/不递归),消除 workspace 内 symlink → 外部目录的递归逃逸
  • metadata 延迟到命中后才 entry.metadata().await?(避免对每个条目都跟随 symlink 读元数据,性能 + 安全双收益)
  • list_dir_recursive 行为对齐(注释 :1798 标注)

⑤ P0 回退完整性 PASS(workspace_root 三处同源):

  • state.rs:490 / tool_registry.rs:275 / mod.rs:282 三处均用 env!("CARGO_MANIFEST_DIR").parent().parent(),无残留 project_root_path
  • state.rs:485-489 加分发适配警告注释(编译期写死不适合分发,转待决策),诚实标注已知限制
  • mod.rs:281 workspace_root_str 额外 canonicalize(trust key 归一化用,与 run_command 默认 working_dir 对齐),用途独立于 is_authorized 比对,无需 strip
  • default_with_root:340/reload:619 空白名单时保留 workspace_root 兜底,向后兼容(开发机自用有效)

⑥ set_allowed_dirs/get_allowed_dirs 一致性 PASS:

  • state.rs:664-665 set_allowed_dirs 返回时过滤 workspace_root(与 get_allowed_dirs:680 一致),内部根不暴露前端
  • AllowedDirsPanel.vue:74 删 isWorkspaceRoot 前端宽松匹配(原 endsWith('/src-tauri') 在分发场景失效),改由后端统一过滤,前后端读写形态一致

⑦ AllowedDirsPanel 即时持久化 + toast PASS:

  • emit toast 通道(Settings.vue @toast="showToast"),替代 console.error 用户零反馈
  • pickDir 选完即加(消除二次点"添加"摩擦)+ 单按钮双态(newDir 空→选目录/有值→添加),UX 改进
  • dirs 由 setAllowedDirs 返回值回填(canonicalize + strip 后真实路径),读写不漂移
  • i18n zh-CN/en settings.ts 4 toast key + allowedDirsSelectDir + desc 对齐(workspace_root→devflow 自身)中英对称

⑧ chat.rs:552 docstring 修正 PASS:PerConvState 已删,改 AppState.session 进程级会话临时授权语义,文档与代码一致

⑨ 回归 PASS:cargo test state:: 18 passed 0 failed(含 3 新增 + 15 原有)。test_blacklist_windows_system32:878 在新"windows 段即拒"逻辑下仍 pass(更早 return true)。test_blacklist_no_false_positive 在 .ssh/.aws/.gnupg 增强后仍 pass(测试用例合法路径不含这些段)


🟡 MED-1:bind_directory / create_project(path) AI 工具路径 reload 残留(注释承诺未兑现)

file:line 问题 建议
tool_registry.rs:545-549(bind_directory handler 注释)+ :523-525(create_project 带 path 路径) 注释承诺"通过 app_handle emit 事件让 state reload,或下次工具调用前 reload 兜底",但代码未实际触发 reload。bind_dir_to_project:375 经 repo.update_field(id, "path", path) 直接写 DB,绕过 project.rs IPC(create_with_binding/update_project/relocate 三处 reload 覆盖不到)。register_project_tools 闭包无 AppState 参数,确实无法直接 reload。reload_allowed_dirs 仅在启动(state.rs:553 init)+ project.rs 三 IPC + set_allowed_dirs(:661)触发,"下次工具调用前 reload 兜底"是空头承诺 — 无该机制(主代 grep reload_allowed_dirs 全 src 印证,无工具调用前置 reload 路径)。后果:AI 调 bind_directory 绑定新目录后,该目录未进内存白名单,后续文件工具仍误弹窗,直到用户手动点 Reload 或重启。 二选一:(a) register_project_tools 闭包捕获 AppState 句柄(若可获取),bind_dir_to_project 后调 state.reload_allowed_dirs().await;(b) process_tool_calls 前置轻量校验:每次工具调用前比对 projects.bind_directory 与内存白名单差集,差集非空触发 reload(兑现"下次工具调用前 reload 兜底"注释)。当前不影响安全(只是 UX 误弹窗,用户可手动 Reload),降 MED。

LOW-1:strip_verbatim 单前缀 strip + 大小写敏感(理论边界,实际安全)

state.rs:471-478 strip_verbatims.strip_prefix(prefix)(prefix 为 \\?\/\\.\/\??\ ASCII 小写),Windows OS 生成的 verbatim 前缀始终固定小写(Rust std canonicalize 保证),大小写敏感 strip 不会漏。仅 strip 一个前缀(无嵌套场景)。理论边界:若 LLM 传入大写 \\?\(非 canonicalize 来源)则 strip 失败 — 但 LLM 传入路径走 validate_path + check_path_authorization 词法层,不经 canonicalize,不会带 verbatim 前缀。实际安全,降 LOW 标注。

LOW-2:reload_allowed_dirs 项目绑定目录读 list_all(无 active 过滤)

state.rs:601-606 reload_allowed_dirsrepo.list_all() 全量项目(含回收站软删项目?)的 bind_directory 合并入 persistent。若软删项目 path 仍被读入,会残留无效授权目录(指向已"删除"项目路径)。建议核验 ProjectRepo::list_all 是否过滤 status != 'deleted',或 reload 时 filter active 项目。当前影响:无害(目录仍存在,授权不影响安全,仅白名单略冗余),降 LOW。


  • 🟡 MED-1: bind_directory / create_project(path) AI 工具路径 reload 残留(注释承诺未兑现,UX 误弹窗,非安全)
  • LOW-1: strip_verbatim 大小写敏感 strip 理论边界(实际 OS 固定小写,安全)
  • LOW-2: reload_allowed_dirs 读 list_all 可能含软删项目 path(白名单略冗余,无害)
  • 待修项回流 todo: 🟡 1 项(tool_registry.rs:545-549 bind_directory/create_project AI 工具路径 reload 兑现 — 二选一:闭包捕获 AppState / process_tool_calls 前置差集 reload)

CR-260620-02 F-09 决策e newConversation 跟进(working tree 未提交:AiChat.vue confirmNewConversation 去中断弹窗+删 isViewingGenerating dead code / useAiConversations.ts newConversation 去 generatingConvs.clear()) — 已审(PASS·2)

  • 复审结论(2026-06-20·审查 agent 独立 git diff + grep/read 核验源码形态): PASS🔴0 🟡0 2
  • 验证: git diff AiChat.vue/useAiConversations.ts 逐行核验 + vue-tsc --noEmit EXIT 0 + 独立 grep 全 src/ 无残留。

① confirmNewConversation 调用路径 PASS: 唯二调用方均正确直达 store.newConversation():

  • AiChat.vue:7 @confirm-new-conversation="confirmNewConversation"(ConversationSidebar emit)
  • AiChat.vue:18 @new-conversation="confirmNewConversation"(TopBar emit)
  • 去弹窗后 handler 体仅剩 await store.newConversation()(:248-250),无残留 isViewingGenerating/confirmDialog 中断逻辑

② isViewingGenerating 删除完整性 PASS: grep 全 src/ 命中 AiChat.vue 已无 isViewingGenerating。其余 3 处命中(MessageList.vue:196 / MaxRoundsCard.vue:42 / DirAuthDialog.vue:50)均为各组件本地独立 computed(store.state.streaming && store.isGenerating(state.activeConversationId)),非 import AiChat 的——AiChat 删除不影响它们。vue-i18n/tsconfig noUnusedLocals 编译通过(vue-tsc EXIT 0)

③ newConversation 去 clear 后旧 conv 收尾 PASS: 事件路由按 conversation_id 正确分发,旧 conv 后台 loop 完成时收尾无依赖 generatingConvs 全清:

  • useAiEvents.ts:461-469 handleEvent 外围 — 非当前会话事件(!isCurrent)仅 AiCompleted/AiError 执行 state.generatingConvs.delete(convId) + loadConversations(),不污染当前视图
  • useAiEvents.ts:362(AiCompleted)/ :415(AiError)— state.generatingConvs.delete(event.conversation_id || '') 按 conv_id 删除(per-conv),非全清
  • 去 clear 后旧 conv 后台完成时按 conv_id 正确从 Set 移除,侧栏态正确刷新(侧栏 isGenerating(id) 读 Set,双会话生成可见)

④ switchConversation 对齐 PASS: newConversation 真对齐 switchConversation 并行语义。两者均:不中断后台 loop / 不清 generatingConvs(per-conv 跟踪保留) / 事件按 conv_id 路由。switchConversation:91-205 grep 核验无 generatingConvs.clear() / 无 loop 杀逻辑

⑤ queue 清空边界 PASS(含 WATCH): state.queue(ai.ts:90)是单一全局数组非 per-conv。drainQueue(useAiSend.ts:246-267)调 sendMessage 续发,路由到 state.activeConversationId(非队列起源 conv)。故 queue 语义是"active-conv 视图的待发意图"非跨 conv 跟踪。newConversation 清 queue = [](:80)实际正确且更安全:防 conv A 排队消息在 conv A 的 AiCompleted 触发 ai-drain-queue 后误发到新 conv B(注释"防旧会话排队消息带进新会话 drain"准确)。🟡 WATCH-1:switchConversation(:91-205)不清 queue,真并发下若 conv A 排队消息 + 切到 conv B,A 的 AiCompleted 会把队列项发到 B(误路由,既有逻辑非本 CR 引入,登记待评估)。

⑥ 后端依赖 PASS: 独立 Read conversation.rs:154-201 ai_conversation_create 确认决策e 后端已落地:

  • :163-166 注释明确「新建会话不杀旧 loop」(B-260615-10 单 loop 软复位废弃)
  • :188 仅切 active_conversation_id = Some(id.clone())
  • :196-198 注释「旧 conv 的 per_conv 不清除(决策e:后台 loop 继续跑)」+ :198 session.conv(&id) 为新会话建独立 per_conv
  • 前端去弹窗/去 clear 跟进无后端依赖,后端先行已就绪

⑦ confirmDialog 基础设施不受影响 PASS: grep AiChat.vue confirmDialog 仍有 5 活跃消费方(:230 confirmDeleteConversation / :236 confirmClearChat / :376 tryForceSend / :545 handleClearContext 等),useConfirm 模块仍 import(:136)。仅 confirmNewConversation 退出消费,无回归

  • WATCH-1: queue 全局数组非 per-conv。switchConversation 不清 queue,真并发下「conv A 排队 + 切 conv B」时 A 的 AiCompleted→ai-drain-queue 会把队列项误发到 B。newConversation 清 queue 是正确收口但两路径行为不一致。建议统一(switchConversation 也清 queue,或 queue 改 per-conv)。既有逻辑非本 CR 引入。
  • LOW-1: stale doc 注释。去弹窗后三处子组件注释仍描述旧行为(「带二次确认」/「生成中走确认弹层」/「生成中需二次确认」):TopBar.vue:163 / ConversationSidebar.vue:226 / ConversationSidebar.vue:242。纯文档不影响行为,建议顺手同步。另 i18n key confirmNewConvWhileGenerating(zh-CN:120 / en:119)已成死 key(0 消费方,仅 2 文件定义残留),可选清理。
  • 待修项回流 todo: 🔴/🟡

CR-260618-01 batch68 未提交改动批量(41文件+754/-2434:虚拟滚动删除+timestamp打戳+audit吞错修复+fetch_models合并+分组折叠+todo精简) — 已审(PASS·🟡1 WATCH)

  • 复审结论(2026-06-18·主代独立 grep/read 核验 diff): PASS🔴0 🟡1 2
  • 验证: git diff HEAD 逐文件核验(41 文件)。

① provider.rs timestamp 打戳 PASS: 非 reasoning_content(已在 74003c 提交)。本批新增 timestamp: Option<i64> 字段 + now_millis_i64() + 全构造器自动打戳。serde(default) 向后兼容注释「provider 映射不读此字段」;单测补 timestamp:None。

② 虚拟滚动删除 PASS(⚠️ WATCH): useAiVirtualScroll.ts 整删(-175)。关键核验: 被删文件内 shouldRender() 已是 void key; return true(恒渲染),即虚拟滚动在删除前已因 IO/RO 时序重叠被禁用。本次是清理已失效死代码非功能回归。AiChat.vue 同步移除所有引用。🟡 WATCH: 长对话(500+ 消息)全量 DOM 无兜底方案,桌面端短期可接受,用户反馈卡顿时再评估。

③ audit.rs+37 PASS(3 项改进): PENDING_APPROVAL_PLACEHOLDER 常量抽取(DRY) | audit_finalize 拆 unwrap_or_default 吞错→Err/None/Err 三路分流(🔒 关键修复) | update_full 失败加 error 日志

④ commands.rs+40 PASS(3 项修复): regenerate 对话校验提前到 generating=true 前(防内存消息破坏) | ai_fetch_models 合并保留用户 weight/enabled label(防排序摇摆) | ai_conversation_delete retain pending_approvals(防死审批残留)

⑤ context.rs+63 PASS(F-05 token 预算修复): estimate_message 累加 parts token(base64/text/url) + 单测。 chars_ratio 0.35 偏保守,后续可校准。

⑥ 前端交互层 PASS(6 项): 分组折叠(toggleGroupFold+v-show) | system 压缩摘要展示 | 复制按钮下沉气泡 | modelOverride 默认最高权重模型 | wasNearBottom 切会话重置 | editEl 函数 ref 修复 v-for 数组收集 bug

⑦ todo.md 精简 PASS: 1200→385 行。P0 区全部 [x] 已修保留;活跃待办完整。 已完成区可进一步归档压缩。

⑧ 待决策.md 括分 PASS: +1065 行来自 todo.md C 类抽离,职责分离清晰。

  • 🟡 WATCH-1: 虚拟滚动删除后长对话全量 DOM 性能(观察级)
  • LOW-1: base64 chars_ratio 0.35 可校准至 ~0.6
  • 待修项回流 todo: 🔴/🟡

CR-260618-02 sweep-fix 6域走查+P3小修+dead_code标注共17处改动(工作区未提交·commit-policy-deferred) — 已审(PASS·2)

  • 复审结论(2026-06-18·主代独立 git diff 核验 17 处): PASS🔴0 🟡0 2
  • 验证: git diff HEAD 逐文件核验全部 17 处改动。cargo check --workspace EXIT 0 / vue-tsc EXIT 0。

sweep-fix 12 项逐一核验:

# 判定
1 context.rs parts token 累加 含图消息 base64 不再绕过预算,单测合理
2 anthropic_compat clone→move DRY+零拷贝
3 scan.rs to_ascii_lowercase 非 ASCII 索引安全
4 scan.rs .is_some_and(!is_null) workspaces null 不再误判
5 adversarial MockProvider reasoning_content 编译修复
6 audit_finalize 拆吞错 Err/None 分流(与 CR-01 联动)
7 delete retain pending_approvals (与 CR-01 联动)
8 PENDING_APPROVAL_PLACEHOLDER 常量 (与 CR-01 联动)
9 ToolCard computed 下沉 DRY parsed
10 AiChat 清虚拟滚动死注释 (与 CR-01 联动)
11 Projects onUnmounted 清 timer 防 timer-leak
12 Knowledge parseContext 去重 冗余 JSON.parse→一次

P3 4 项逐一核验:

# 判定
13 wasNearBottom 切会话重置 防 A→B 误触发 collapseAllToolLists
14 TaskDetail wfResultTimer 防卸载后写已销毁 ref
15 Ideas .net-sentiment.neutral CSS 缺口补全
16 ai_node schema required=[] WATCH: 运行时允许留空,需确认无其他消费方强制校验 required
17 openai_compat #[allow(dead_code)] 标注意图消 warning
  • WATCH-1: ai_node.rs required=[] 影响面——若其他 Node Schema 消费方有 required 白名单校验则需同步放宽
  • 待修项回流 todo: 🔴/🟡

CR-260618-03 SW-10 reqwest Client 构建 DRY(工作区未提交·commit-policy-deferred·1 文件域 df-ai) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代自测 + 独立 grep 核验源码形态): PASS🔴0 🟡0 0
  • 验证: cargo check -p df-ai EXIT 0(无 warning) / cargo test -p df-ai 112 passed 0 failed(不回归)。

改动 1 处(3 文件 df-ai crate 内):

# 判定
1 lib.rs 加 pub(crate) build_provider_client()(connect_timeout 30s + 回退 Client::new + tracing::warn) 纯抽函数,与原两处逐字一致
2 openai_compat.rs Provider::new 调 crate::build_provider_client() 替原 7 行 DRY,行为不变
3 anthropic_compat.rs Provider::new 同替换 DRY,行为不变

副作用独立核验(grep 源码形态):

  • use reqwest::Client 两文件仍用于 Provider struct 字段(self.client:Client),非 unused

  • use tracing::{..., warn} 两文件仍多处用(openai:503 同步调用 warn / anthropic:183/330/364/497/521/535 流式 warn),非 unused

  • use std::time::Duration 两文件仍用于 :484/:481 .timeout(Duration::from_secs(60)),非 unused

  • 替换前两处 Client::builder().connect_timeout(std::time::Duration::from_secs(30)).build().unwrap_or_else(...) 逐字一致,抽函数后调用点等价

  • 待修项回流 todo: 🔴/🟡

CR-260618-04 SW-16 审计 status 透传 + SW-06 ToolCard watch 合并(工作区未提交·commit-policy-deferred·2 文件) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代自测 + 独立 grep 核验源码形态): PASS🔴0 🟡0 0
  • 验证: cargo check --workspace EXIT 0(仅 4 pre-existing dead_code 基线 warning) / cargo test -p devflow 92 passed 0 failed / vue-tsc EXIT 0。

改动 2 项(2 文件):

# 判定
1 SW-16 audit.rs find_cached 加 audit_repo 参数 + async + 返回 (content,status);查 find_by_tool_call_id 拿缓存来源真实 status 透传 audit_tool_call find_by_tool_call_id 已存在复用,审计精度改进零回归
2 SW-06 ToolCard.vue 双 watch(props.tc.status) 合并(放 :607 三 ref 已定义避 TDZ + immediate 语义) immediate 边界安全(approving 初始 false/timer null),行为等价

SW-16 独立核验(grep 源码形态):

  • find_cached 签名 async fn(session, audit_repo: &AiToolExecutionRepo, tool_name, args) -> Option<(String,String)>
  • 调用点 :625 if let Some((cached, status)) = find_cached_high_risk_result(session, &audit_repo, &draft.name, &args).await (process_tool_calls 内 audit_repo :555 可达)
  • audit :639 audit_tool_call(..., &status, ...) 透传非固定 "completed"
  • find_by_tool_call_id 返回 AiToolExecutionRecord.status: String(models.rs:211),rec.status 直接访问
  • fallback completed(审计缺失/查询失败不阻塞去重)

SW-06 独立核验:

  • 合并 watch 放原 watch2 位置(approving/approvingTimer/cmdOutputExpanded 三 ref 已定义)避 TDZ

  • immediate:true 保留原 watch1 语义;immediate callback 初始 status 下 approving=false/timer=null 无副作用

  • 待修项回流 todo: 🔴/🟡

CR-260618-05 SW-07 AiChat currentText 双 watch 合并(工作区未提交·commit-policy-deferred·1 文件) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代自测 + 独立 grep 核验): PASS🔴0 🟡0 0
  • 验证: vue-tsc EXIT 0。

改动 1 项(1 文件):

# 判定
1 AiChat.vue 删 :2040 watch(currentText→onContentChange) + :2047 合并 callback(onContentChange 先 scheduleStreamParse 后,保持原注册顺序) 同源同顺序同条件,行为等价

独立核验:

  • 原 :2040 watch(currentText, onContentChange) + :2047 watch(currentText, (text)=>{...}) 注册顺序 :2040 先 :2047 后(Vue 同源 watch 按注册顺序执行)

  • 合并 callback 内 onContentChange() 先 + scheduleStreamParse 后,保持同顺序

  • 无 immediate(两原 watch 均无),无 TDZ(onContentChange :2032/scheduleStreamParse :912 均在前定义)

  • :2039 watch(messages.length→onContentChange)不同源保留

  • 待修项回流 todo: 🔴/🟡

CR-260618-06 SW-19 ToolCard combineOutputs 抽函数 + SW-20 Ideas parseTags 复用(工作区未提交·commit-policy-deferred·2 文件) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代自测 + 独立 grep 核验源码形态): PASS🔴0 🟡0 0
  • 验证: vue-tsc EXIT 0。

改动 2 项(2 文件):

# 判定
1 SW-19 ToolCard.vue 抽 combineOutputs(stdout, stderr) 共享函数;combineAndTruncateLines + cmdOutput computed 都复用 DRY 收敛,行为不变
2 SW-20 Ideas.vue 删本地 parseTags(IdeaRecord),import stores/knowledge.ts parseTags;3 调用点改传 .tags DRY 复用 store 版,签名收敛

独立核验(grep 源码形态):

  • ToolCard combineOutputs 定义 :463 + 调用 :472/:652

  • Ideas parseTags import :214 + 调用 currentIdea.tags(:148/:149) / i.tags(:278)

  • 待修项回流 todo: 🔴/🟡

CR-260618-07 SW-08 AuditLog.vue i18n 化(工作区未提交·commit-policy-deferred·3 文件) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代自测 + 独立 grep 核验 + i18n message 盲点检查): PASS🔴0 🟡0 0
  • 验证: vue-tsc EXIT 0。

改动 3 项(3 文件):

# 判定
1 新建 src/i18n/en/auditLog.ts(auditLog 命名空间:title/refresh/desc/loading/empty + col×7 + pager×4 + risk×3 + status×6 + decided×2) 对齐 ideas.ts 风格
2 新建 src/i18n/zh-CN/auditLog.ts(中文对应,index glob 自动聚合) 中英对称
3 AuditLog.vue 引入 useI18n + template 15 处文本 t() 化 + riskLabel/statusLabel/decidedLabel 改 t() class 映射不国际化(保留),模式对齐 Dashboard/Knowledge/Ideas

独立核验(vue-tsc + i18n message 盲点):

  • vue-tsc EXIT 0

  • useI18n 模式对齐项目现有(Dashboard/Knowledge/Ideas/ProjectDetail 同款 const { t } = useI18n())

  • message 值仅 {n} 标准插值(page: '第 {n} 页'),无 @//裸 {}(避 prod runtime 报错盲点)

  • riskLabel/statusLabel/decidedLabel 未知值走 vue-i18n 默认 fallback,但 risk/status/decided 后端值固定不触发

  • 待修项回流 todo: 🔴/🟡

CR-260618-08 SW-09 ai_node provider 三件套 DRY(工作区未提交·commit-policy-deferred·1 文件 df-nodes) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代自测 + 独立 grep 核验源码形态): PASS🔴0 🟡0 0
  • 验证: cargo check -p df-nodes EXIT 0(无 warning) / cargo test -p df-nodes 82 passed 0 failed 1 ignored(不回归)。

改动 4 处(1 文件 df-nodes/src/ai_node.rs):

# 判定
1 resolve_and_parse(db,config,inputs) 合并 resolve_provider+parse_params 两步 AiNode/SelfReview 逐字重复消除
2 provider_from_params(&p) 封装 build_provider 5 行 同上
3 AiNode execute :254-262(9 行)→ 2 行(resolve_and_parse + provider_from_params) 行为零变化
4 SelfReview execute parse(:465-466)+ build(:493-498)→ resolve_and_parse + provider_from_params(中间 task 读保留) p 生命周期跨 task 读正确

独立核验(grep 源码形态 + 测试):

  • messages/temperature 差异不抽(AiNode 用 p.system_prompt/p.temperature;SelfReview 强约束 Some(0.0)+REVIEW_SYSTEM_PROMPT)

  • helper 仅收 provider 三件套,差异区各节点自有

  • 原 resolve_provider/parse_params fn 保留(test 直接调用 :781+),helper 是上层封装非替代

  • 待修项回流 todo: 🔴/🟡

CR-260618-09 UX-260618-04 前端删 cost/intel 标签(工作区未提交·commit-policy-deferred·4 文件) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代自测 + 独立 grep 核验无残留): PASS🔴0 🟡0 0
  • 验证: vue-tsc EXIT 0 / grep tagCost|tagIntel 全 src/ 无残留。

改动 4 处(4 文件):

# 判定
1 ProviderPanel.vue 删 tag-cost/tag-intel 两 span(:114-115) 模态/能力/探测源标签保留
2 AiChat.vue modelOptionLabel 删 intel/cost 拼接,只返 label/model_id + 注释更新 下拉选项不再显示瞎猜档位
3 i18n en/settings.ts 删 tagCost/tagIntel + 注释 4 维度→模态+能力
4 i18n zh-CN/settings.ts 同

独立核验(自主裁决依据 + grep):

  • 用户已全局决策去掉 cost/intel(B-260618-03 背景)→ 前端删标签是决策落地

  • grep 全 src/ 确认 tagCost/tagIntel 仅 ProviderPanel + AiChat 引用,删后无残留

  • ModelConfig.cost_tier/.intelligence 字段保留(后端 B-03 解耦后清),前端先删引用独立安全

  • i18n 删 key 非加,message 编译盲点不适用

  • 待修项回流 todo: 🔴/🟡

CR-260618-10 6项编码批·router解耦cost/intel+model_probe去瞎填+executor TOCTOU+审批占位终态化+knowledge_events query+CostTier清理(workflow wexu1isx1·6 agent·主代修SW-02借用+核查·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·workflow wexu1isx1 + 主代 cargo/test/vue-tsc 全量验证 + 独立 grep 核验): PASS🔴0 🟡0 1
  • 验证: cargo check --workspace EXIT 0(5 pre-existing dead_code warning,非本批) / cargo test df-ai 109 passed 0 failed / cargo test df-ai-core 24 passed / cargo test df-workflow 23 passed / cargo test df-storage 全 passed / vue-tsc EXIT 0。

6 项逐项核验:

# 判定
1 B-03 router 解耦:TaskRequirements 删 min_intelligence/max_cost + select 删步骤4/5 filter + max_by_key 纯 weight 删 Reverse + 9 调用点删传参 + import 清理 + test 改写(删3用例 112→109 + all_dimensions 改 d 胜) 主代 grep 核验 min_intelligence/max_cost 代码层 0 残留(仅注释 3 处)
2 B-04 model_probe 去瞎填:heuristic_infer cost/intel 中性默认 Medium/Standard + presets/models.json 17条删两字段 功能维度 modalities/capabilities 保留,中性默认对齐 Default impl
3 B-05 CostTier::Free 清理:删 Free 变体 + test 同步 + 主代补前端 types.ts 删 'free' 对齐 grep CostTier::Free 代码 0 残留(仅 todo 任务条目)
4 SW-01 executor TOCTOU:Ok 分支 emit NodeCompleted 移入 !is_cancelled + 取消 emit NodeCancelled 对齐 Err + outputs.insert 保留(不中止后续) cargo test df-workflow 23 passed(test_cancelled_node_skips/emits 不回归)
5 SW-02 审批占位终态化:finalize_pending_placeholders helper + 5 处 .clear() 前替换终态 主代修 agent E0502 借用冲突 + cargo check EXIT 0
6 SW-04 knowledge_events query:KnowledgeEventsRepo::list_recent ORDER BY timestamp 兜底 新方法预防性(无活跃调用方),df-storage test passed

主代核查·审查防污染印证:

  • workflow agent 全按铁律不跑 cargo(只 grep 自验)。SW-02 agent riskNote 第4点预判"disjoint field borrow 应无问题"错误 —— 主代 cargo check 捕 5 个 E0502(函数参数列表不做 disjoint 推断,只在函数体内合法)。主代修 helper 签名 &mut AiSession 整体(函数体内 disjoint)+ 调用点 &mut *session(DerefMut 解 guard)。印证:agent 不跑 cargo 的 riskNote 是猜测,主代 cargo 必验(同 Sprint 22 教训)。

  • B-03 agent 自验 grep 0 残留准确(主代独立 grep 印证)。

  • df-ai test 109 = 112 - 3(B-03 删 intelligence_below_min/max_cost_filters/max_cost_none 3 用例)对齐。

  • B-05 riskNote 标注前端 types.ts 'free' 未同步 → 主代补删对齐 Rust(防 DB "free" 值反序列化边界 + 类型宽松)。

  • WATCH-1: B-03 行为变更 —— title/compress 失去 max_cost 约束后纯 weight 选模型,同 weight 并列时 max_by_key 返回最后一个(rust Iterator 语义,池顺序决定)。需用户核对 Settings weight 配置合理(用户已全局决策接受,核对 weight 即可)。

  • 待修项回流 todo: 🔴/🟡

CR-260618-11 batch69 全面走查(53文件+1373/-2890:6域全量·Workflow wf_8f2a96d3-dc7·5路并行+安全扫描+主代独立核验降级) — 已审(ISSUES·🔴1🟡44) → 🔴已修(主代闭环 2026-06-18)

  • 结论(2026-06-18·Workflow 5路并行 + 主代独立 grep/read 核验 + High 项逐条源码佐证降级): ⚠️ ISSUES🔴1 🟡4 4
  • 验证: git diff HEAD 53 文件 / cargo check --workspace EXIT 0 / vue-tsc EXIT 0 / DOMPurify 覆盖确认

🔴 必须修复 (1):

# file:line 问题 修复
1 commands.rs:289 ai_approve 幂等路径仍 unwrap_or_default 吞 DB 错 — audit.rs 已修(B-260617-17 同款 match 三路分流),但 commands.rs:290 find_by_tool_call_id(...).await.unwrap_or_default() 仍在。DB 故障时 Err→None→返回「未找到挂起审批」误导用户 对齐 audit_finalize 模式: match { Ok(Some)→rec, Ok(None)→return Err, Err(e)→error!+return Err }

🟡 建议改进 (4):

# file:line 问题 建议
2 context.rs:57 base64 chars().count() × 0.35 显著高估图片 token(OpenAI 按像素非字节),含图消息被过度裁剪 加注释说明偏保守粗估,或降至 0.10~0.15
3 types.ts:207 IntelligenceTier i18n 标签同删但无注释说明(CostTier 有注释 rationale) 加注释对齐 UX-260618-04 决策记录
4 types.ts:303 AiMessage.role 新增 'system',前端 .role === / switch 完备性待全量验证 grep 全 src/ 确认无路径将 system 当 user 可编辑/可发送
5 audit.rs:478 find_cached_high_risk_result N 次串行 DB 查询在 session lock 内High risk 工具多时锁持有线性增长 批量预取或文档标注瓶颈防后续加重量级 IO

可选优化 (4):

# file:line 问题
6 provider.rs:134 now_millis_i64 i64 截断理论可能(实际 i64::MAX=2920亿年不触发)unwrap_or(0) 兜底值语义不纯
7 AiChat.vue:414 v-if/else-if 链未知 role 落入 AI 分支(后端可控,既有逻辑)
8 tool_registry.rs:111 Path Traversal Unicode/8.3/ADS 绕过盲区(Tauri 桌面+审批双防线,门槛高)
9 AiChat.vue:423 system 消息 v-html XSS 向量(已确认 useMarkdown.ts:192 _purify.sanitize() 覆盖DOMPurify 默认配置防 script/iframe/object)

主代核验印证(防污染):

  • H2 🔴 确认: commands.rs:290 grep 命中 unwrap_or_default(),与 audit.rs 修复口径不一致

  • H4/H8 XSS 降级🟡: useMarkdown.ts:192 确认 _purify.sanitize(_marked.parse(wrapped)) 覆盖所有角色(含 system)

  • H1 截断 降级: i64::MAX = 9.22×10¹⁸ ms ≈ 2920 亿年后,当前 1.7×10¹² ms 远在范围内

  • H9 Path Traversal 降级🟡: Tauri 桌面应用非网络服务 + High risk 审批双重防线

  • 待修项回流 todo: 🔴 1 项 (commands.rs:289 ai_approve 吞错修复)

主代修复闭环(2026-06-18·workflow w999qdu86 3 agent + 主代核查):

  • 🔴1 BUG-260618-11 已修:commands.rs:289-303 ai_approve None 分支 match 三路分流(Ok(Some)→状态判断 / Ok(None)→未找到 / Err(e)→tracing::error+明确 DB 故障 Err),对齐 audit.rs audit_finalize。原 unwrap_or_default 已消。行为变更:DB 故障现返「查询审批记录失败(DB 故障),请重试」明确错误(原误为「未找到挂起」),同属 Err 分支语义不变。
  • 🟡4 处理:#2 context.rs:56 chars_ratio 0.35 加注释标注偏保守(值未变,降值属行为变更本次不做) / #3 types.ts:209 IntelligenceTier 加注释对齐 CostTier + UX-260618-04 决策 / #5 audit.rs:402 find_cached_high_risk_result 加性能注记(session lock 内串行 DB 查询线性增长,批量预取架构改暂未做) / #4 system role 完备性 grep 核验 PASS(14 处 role 赋值全 user/assistant 无 system;2 处比较点 AiChat.vue:421/2545 只读渲染;发送路径仅 push user/assistant,system 不可编辑不可发送)
  • 主代核查:cargo check --workspace EXIT 0(4 pre-existing dead_code warning 基线)+ vue-tsc EXIT 0 + 独立 grep 印证 agent selfCheck 无污染
  • 🔴1 待修项已闭环,todo BUG-260618-11 已销账

CR-260618-12 ToolCard.vue 工具结果渲染 8 项(workflow w2drz3ppo·8 agent 并行核验 isReal 8/8+主代统筹实施·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·workflow w2drz3ppo 8 agent 并行核验 + 主代统筹实施 + vue-tsc 全量验证 + 独立 grep 核验): PASS🔴0 🟡0 1
  • 验证: vue-tsc --noEmit EXIT 0 / grep 印证 ToolCard.vue 22 处新字段 + i18n 18 处新 key 全落地。

8 项实施(主代统筹:合并冲突/修死代码位置/补漏字段):

# 判定
1 UX-05 run_workflow 显 execution_id(formatToolResult+toolResultSummary 加 case,删误导 workflowHint,旧 key 保留防引用)
2 UX-06 patch_file 模板分支渲染 diff 红绿行(复用 ai-tool-diff-pre)+resultDiffLines computed(parsed.diff 截断 120 行)
3 UX-07 delete_file 软删补 backup_path(formatToolResult+toolResultSummary+i18n 加槽)
4 UX-08 read_file 模板 has_more 显「shown/total 行,已截断」
5 UX-09 advance_task 加 review_rounds(>0 显退回累加)
6 UX-10 list_trash(被 UX-12 list_* 重写吸收,无独立改动) 吸收
7 UX-11 list_directory truncated 显截断提示
8 UX-12 list_* 重写修 Array.isArray 对象误判+结构化列表+file_info modified+write_file old_size/encoding+rename_file cross_volume/bytes_moved

主代统筹裁决(审查防污染,不盲应用 agent edits):

  • 冲突吸收:UX-10/12 都加 list_trash case + trashCount(文案冲突),采纳 UX-12(修 Array.isArray 对象误判真实 bug——后端返 {items,total,has_more} 对象非数组,原判断恒 false 致折叠态无计数),UX-10 整条吸收
  • 死代码位置修正:write_file 有专属模板分支拦截,formatToolResult write_file case 不触发;old_size/encoding 改模板分支(主渲染路径)非 agent 建议的 formatToolResult case
  • type 去重:12 缺字段合并 ToolResult type 一处 Edit(agent 说缺 truncated/review_rounds/bytes_moved 实际已有,只补真缺)
  • task_id 漏列拦截:UX-05 agent 用 r.task_id 但 typeFieldAdditions 漏,vue-tsc 拦 TS2339,主代补 task_id(印证 agent edits 自身不一致,vue-tsc 必验)

审查防污染印证:8/8 isReal=true,每份 selfCheck 独立 grep tool_registry.rs 后端返回印证。todo line 号漂移(diffLines agent 说 :678 实际 :696)被主代 Read 核验拦截。单文件域 ToolCard.vue 主代统筹实施(避免并行冲突),workflow 价值在 8 工具并行核验 fan-out + 审查防污染独立核验。

  • WATCH-1: list_directory/list_* 截断文案硬编码 {n:1000}(后端 tool_registry.rs:880 max_entries=1000),后端上限变更需同步前端文案
  • 待修项回流 todo: 🔴/🟡

CR-260618-13 SMELL-P0-1吞错降级加warn+B-260618-06表格CSS+coordinator空壳评估(workflow w5siwnipj·6 agent并行核验+主代统筹实施·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·workflow w5siwnipj 6 agent 并行核验 + 主代统筹实施 + cargo/vue-tsc 全量验证 + 独立 grep 核验): PASS🔴0 🟡0 1
  • 验证: cargo check -p devflow EXIT 0(4 pre-existing dead_code warning 基线:PendingApproval/state 字段,非本批) / vue-tsc --noEmit EXIT 0。

4 项实施(主代统筹)+ 2 项 no-action:

# 判定
1 B-260618-06 markdown 表格 display:block CSS(ai-md.css:59 全局根因 + AiChat.vue:3366 局部) 两处移除 display:block/overflow-x:auto,table 恢复 display:table 列对齐,气泡 .ai-msg-bubble--ai.ai-md overflow-x:auto 承载宽表格滚动
2 SMELL-P0-1 agentic.rs:348 provider 池吞错 match + tracing::warn 降级(空 Vec 走原空池兜底零行为变更)
3 SMELL-P0-1 knowledge_inject.rs:130,139,312 检索/解析吞错 三处 match + tracing::warn 降级(空 Vec 不注入/提炼跳过零行为变更)
4 SMELL-P0-1 idea.rs:292-296 tags JSON 解析吞错 match + tracing::warn 降级(空 tags 继续评估,tags 影响 scoring ±0.5~1.5 但评估不应因坏 tags 阻塞)
5 SMELL-P0-1 audit.rs:344(吞错) 误报 no-action(audit_finalize CR-260618-11 已修三路 match,line 漂移+旧版本)
6 SMELL-P2-4 coordinator.rs 空壳 no-action(line 3 模块注释已有「⚠ B 路线占位...勿删」标注齐全,零外部调用,保留 B 路线预留点)

主代统筹裁决(审查防污染,独立 grep 核验 agent selfCheck):

  • agent selfCheck 6/6 准确(主代独立 Read ai-md.css/knowledge_inject.rs/idea.rs 印证 currentCode 逐字无误,behaviorChange=false 判定可信:加 warn 不改返回值,空 Vec 仍走原降级路径)

  • audit 误报印证审查防污染价值:todo line 声明 audit.rs:344 吞错,独立 grep 核验实为 CR-11 已修的三路 match(:337-347),unwrap_or_default 仅剩 :274(from_str)/:499(to_string)非 DB——line 漂移+旧版本双重原因,workflow agent 独立核验拦截

  • CSS 方案主代补强:agent 建议全局 ai-md.css 移除 display:block,主代核验确认全局影响面(ProjectDetail/Ideas/Knowledge/TaskDetail 同引用),宽表格滚动仅 AiChat 气泡场景需补 overflow,其他页 markdown 表格少宽表可接受;采纳 agent 核心理念(移除 display:block 恢复列对齐)+ 主代补 AiChat 气泡 overflow-x:auto

  • WATCH-1: SMELL-P0-1 58 处 unwrap_or_default 全量排查降 P2(5 高危已清,余为 Option 取默认/JSON 序列化等低危,非紧急)

  • 待修项回流 todo: 🔴/🟡

CR-260618-14 安全并行批·df-execute补测试+前端ErrorBoundary+6篇文档实施状态标注+executor run评估(workflow wowdnw4ba·5 agent并行·主代独立核验·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·workflow wowdnw4ba 5 agent + 主代 cargo/test/vue-tsc 全量验证 + 独立 grep 核验): PASS🔴0 🟡0 1
  • 验证: cargo check --workspace EXIT 0 / cargo test -p df-execute 5 passed 0 failed / vue-tsc --noEmit EXIT 0。

5 项逐项核验:

# 判定
1 SMELL-P0-4 df-execute 补测试(tests/shell.rs 5 #[tokio::test] 覆盖成功/非零退出/超时/env注入/working_dir + shell.rs:55 过时TODO注释清理) execute逻辑零改动,主代 cargo test 5 passed
2 SMELL-P2-2 前端 ErrorBoundary(main.ts app.config.errorHandler + ErrorBoundary.vue onErrorCaptured 降级UI+重试 + i18n error.ts×2 中英对称) 未嵌入仅提供能力,App.vue 未改零行为变更
3 SMELL-P1-8 文档组1(B-03/AiNode自审/类型对齐 3篇加「实施状态(2026-06-18核对)」标注段) 不改设计正文,DOC2 行号经独立 grep 修正
4 SMELL-P1-8 文档组2(F-01 标 cost/intel 解耦改方向/F-05 标数据模型偏离/secret下沉 标方案B全落地) router.rs:27-35/56-63 + provider.rs:97/105-106 file:line 佐证
5 SMELL-P2-1 executor run 重构评估 no-action(SMELL描述过时:run方法实测 151行+2参 非描述282行+8参,SW-01 TOCTOU保留 line 136/138-153,描述系历史形态)

主代核查·审查防污染印证:

  • df-execute tests: cargo check --workspace 默认不编译 tests target,主代补 cargo test -p df-execute 验证 5 测试编译+逻辑双通过(防 check 假绿)。

  • executor no-action 印证铁律价值:agent 现场读源码发现 SMELL-P2-1 描述(282行+8参)系历史形态,当前 run 方法 151行+2参已可接受,不盲改(避免动 SW-01 TOCTOU 敏感逻辑引入回归)。源码形态 > todo 描述。

  • 6 篇文档标注段独立 grep 核验全在(line 7-11),file:line 佐证真实。ErrorBoundary.vue onErrorCaptured:21 + main.ts errorHandler:12 grep 命中。

  • WATCH-1: ErrorBoundary.vue 当前未嵌入任何组件树(仅 main.ts errorHandler 全局兜底生效),后续需包裹易错子树时按需引入(零行为变更设计意图)

  • 待修项回流 todo: 🔴/🟡

CR-260618-15 B-260618-23最大轮次修法B+SMELL-P1-2/P1-3样板消除评估(workflow wuirgcxoy·3 agent·主代独立核验·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·workflow wuirgcxoy 3 agent + 主代 vue-tsc 验证 + 独立 grep 核验): PASS🔴0 🟡0 1
  • 验证: vue-tsc --noEmit EXIT 0(B-260618-23 前端) / P1-2/P1-3 no-action 无 rust 改动 cargo 不需。

3 项逐项核验:

# 判定
1 B-260618-23 Agentic 最大轮次修法 B(App.vue:215-224 onMounted loadAll 后加 setAgentMaxIterations/setAgentMaxRetries 恢复同步 clamp 对齐 GP + GeneralPanel:266 双保险) done 修bug正向,主代 vue-tsc EXIT 0
2 SMELL-P1-2 .map_err 抽 helper no-action(前提过时:err_str helper 已存在 mod.rs:23·纯 to_string 样板 0 残留·101 处已用 err_str·主代 grep 印证)
3 SMELL-P1-3 useTauriCommand composable no-action(前提不成立:views 仅 AuditLog:102 直接 invoke·invoke 已统一 api 层·try-catch 副作用异构·主代 grep 印证)

主代核查·审查防污染印证:

  • 两 no-action 印证源码形态 > todo 描述:agent 现场读源码发现 err_str helper 早前已落地(P1-2)、invoke 已统一 api 层(P1-3),不盲改。主代独立 grep 印证:err_str 在 mod.rs:23 / to_string 残留仅 mod.rs:22 注释字面量 / views invoke 仅 AuditLog:102。

  • B-260618-23 修 bug 行为变更正向(重启后正确恢复持久值),clamp 边界与 GeneralPanel 逐字对齐(Math.min(50,Math.max(1,||10)) / Math.min(10,Math.max(0,??3))),GeneralPanel onMounted sync 保留双保险(IPC 幂等)。

  • WATCH-1: B-260618-23 修法 B 依赖 appSettings SQLite 持久化(前端已落 DB),重启后 App.vue loadAll 读真值同步后端。若用户报告仍未生效,查后端 set IPC 是否真落 AtomicUsize + loadAll 是否在发消息前完成

  • 待修项回流 todo: 🔴/🟡

CR-260618-16 SMELL-P1-1 crates/全域unwrap排查+P1-4聚合评估+P2-3死字段评估(workflow w8774xcev·7 agent·主代独立核验·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·workflow w8774xcev 7 agent + 主代 cargo/test 验证 + 独立 grep 核验): PASS🔴0 🟡0 2
  • 验证: cargo check --workspace EXIT 0 / cargo test -p df-workflow EXIT 0(dag 注释不影响)。

7 项逐项核验:

# crate 判定
1 df-ai-core+types no-action(31处unwrap全#[cfg(test)],生产零危险)
2 df-ai no-action(21处全测试,2处生产expect已注释不变量 context:499/model_probe:34)
3 df-storage no-action(~50处全测试,全程?+map_err健康)
4 df-workflow done(dag.rs:106/135两处运行时unwrap加safe注释,SW-01保留)
5 df-execute no-action(unwrap零)+P2-3评估(env死字段5构造点全空HashMap)
6 df-nodes no-action(75处全测试)+P1-4评估(缓做)+P2-3评估(NodeOutput.metadata死字段)
7 df-ideas+project no-action(49处全测试)

主代核查·审查防污染印证:

  • unwrap 排查绝大多数 no-action 印证 crates/ 错误处理已健康(之前多批重构清理):生产 unwrap 全在 #[cfg(test)] 或已是安全 expect/unwrap_or。仅 df-workflow dag.rs 加 2 处 safe 注释。主代独立 grep 核验 dag.rs:106/135 注释存在 + SW-01 TOCTOU(:136/141/150/163-166)完整保留。

  • P2-3 描述错误修正:todo:185 写「NodeContext.metadata」有误,源码为准 NodeContext(node.rs:13-26)无 metadata 字段,含 metadata 的是 NodeOutput(node.rs:34)且是死字段(2构造点初始化空+0消费)。ShellRequest.env 同死字段。源码形态 > todo 描述。

  • P1-4 评估:df-nodes 拆 df-task-state-machine 缓做(task_state_machine.rs 零 crate 依赖+单一消费者+拆出不解锁循环依赖,收益不抵成本)。

  • WATCH-1: crates/ 生产 unwrap 仅 df-workflow 2 处加注释,若未来新代码引入生产 unwrap 需重新评估

  • WATCH-2: NodeOutput.metadata + ShellRequest.env 均死字段(0消费),如需清理建议直接删字段(降级方案)或改结构体(纯整洁低优先)

  • 待修项回流 todo: 🔴/🟡


CR-260618-17 SMELL-P1-7 bool评估+P1-5下沉评估+NodeOutput.metadata实删+ShellRequest.env评估(workflow weckqp9mv·4 agent·主代独立核验·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·workflow weckqp9mv 4 agent + 主代 cargo 验证 + 独立 grep 核验): PASS🔴0 🟡0 1
  • 验证: cargo check --workspace EXIT 0(4 warning 全 pre-existing dead_code 基线·非本次引入)。

4 项逐项核验:

# 任务 判定
1 SMELL-P1-7 no-action(enum 负收益·bool 直映射 DB·2 crud+2 IPC 调用者紧邻定义·真正歧义前端 store 拆 TS 方法非推 enum·IPC 引 enum 触 Tauri 序列化 prod 报错)
2 SMELL-P1-5 no-action(advance_task_atomic 已领域层 df-nodes/task_advance_node.rs:43·3 IPC 纯转发;record_to_idea 下沉违反依赖倒置·df-ideas→df-storage 倒依赖·IPC 是 adapter 正确归属)
3 NodeOutput.metadata done(实删 node.rs 字段+2 构造点 empty()/from_value()·0 消费者·零行为变更)
4 ShellRequest.env no-action(前提错·测试 #4 execute_env_injection 实塞 KV 透传子进程验证·活跃消费链非死字段)

主代核查·审查防污染印证:

  • 4 项 3 个 no-action 印证代码库健康。仅 NodeOutput.metadata 实删(纯死代码零消费)。主代 grep 核验 node.rs struct 仅 data 字段 + empty/from_value 唯二构造。

  • P2-3 深化:批次3 评估为「保留或删·低优先」,批次4 实删 metadata(env 经核为活跃消费链保留)。

  • WATCH-1: 其他会话并发改主代码,本核查为 cargo check 快照·源码形态可能已变(memory review-batching-worktree-transient 教训)

  • 待修项回流 todo: 🔴/🟡


CR-260618-18 UX-260618-13 stripMd 列表摘要 MD 剥离(主代串行·规避 AiChat God 文件·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代串行实施 + vue-tsc 验证): PASS
  • 验证: vue-tsc EXIT 0。

改动:

  • 新建 src/utils/markdown.ts stripMd(剥离粗体/斜体/删除线/标题/行内代码/代码块围栏/列表/引用/链接/图片等 MD 语法·保留文字内容·空输入返空串)。
  • Knowledge.vue:71(item.content) / Projects.vue:148(project.description) / Ideas.vue:47(idea.description slice) 三列表摘要接入 stripMd。
  • 修自引入 bug:首版注释含 */ 子序列(**/__/*)致 JSDoc 块注释提前闭合 → TS1011/1127·改安全中文措辞。

主代自测·审查防污染:

  • AiChat 标题 strip 子项(待核 title.rs)规避 God 高频文件·留待并发期后。

  • 前端纯新增 util + 单行插值·vue-tsc EXIT 0 独立核验。

  • 待修项回流 todo: 🔴/🟡


CR-260618-19 idea.rs:111 promoted_to is_some+unwrap 改 if let Some(Explore2 巡查核验真项·主代串行·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代串行 + cargo 验证 + 独立 grep 核验): PASS
  • 验证: cargo check --workspace EXIT 0(4 warning 全 pre-existing dead_code 基线·非本次引入)。
  • 改动: promote_ideaif record.promoted_to.is_some() { return Err(format!("灵感已立项: {}", record.promoted_to.unwrap())); }if let Some(promoted_to) = &record.promoted_to { return Err(format!("灵感已立项: {}", promoted_to)); }。消除 is_some() 守卫后冗余二次 unwrap·改借用·零行为变更。

主代核查·审查防污染印证:

  • Explore2 开放式扫 5 候选·独立 grep 核验后仅此项真:script_node.rs:46 unwrap_or(-1) 已安全不 panic(注释 ROI 低) / AuditLog.vue:96 误判(空数据 length=0≠PAGE_SIZE→hasMore 正确 false) / Tasks.vue:144 projectFilters 与 getProjectName 不同用途非重复 / ConnectionPanel.vue 文件不存在(Explore2 虚构)。防污染拦下 4 误报·命中率 20%。

  • 待修项回流 todo: 🔴/🟡


CR-260618-20 批次6 测试补测 scan/router/migrations(workflow w07wahoti·3 agent 并行·零行为变更·主代 grep 核验测试数一致·工作区未提交·commit-policy-deferred) — 已审(PASS·主代核验)

  • 结论(2026-06-18·workflow w07wahoti 3 agent + 主代 grep 核验): PASS
  • 3 crate 补测·零行为变更(只加 #[cfg(test)] mod·不改生产):
    • df-project scan.rs +10(normalize_path 跨平台×3 / is_monorepo workspaces:null 防回归×3 / collect_images 徽章过滤×4)·agent 自报 cargo test 25 passed
    • df-ai router.rs +3(多模态子集[Text,Vision]过滤 / enabled=false 混池 / 空池 select() 返 None)·自报 112 passed
    • df-storage migrations.rs +4(column_exists 列存在/不存在/表不存在/精确匹配·:memory: SQLite)·自报 33 单测+11 集成
  • 主代 grep 核验(防污染·源码形态 > check 快照·memory review-batching-worktree-transient):scan.rs #[test]=25(15旧+10新) / router.rs=15(12旧+3新) / migrations.rs=4(新增)·与 agent 自报全一致。

主代核查·审查防污染印证:

  • 零行为变更:cfg(test) 隔离·concurrent 会话改生产不影响 test 编译。agent 三段式自验 cargo(memory workflow-cargo-timeout-wrap)·grep 测试数一致强佐证。

  • 测试路 Explore 10 候选核验:shell.rs 批次1 已覆盖(过时) / human_node reject 已充分覆盖(:833-848·低估) / adversarial 有 mod tests(错报 0) / ai_tools+migrations+前端utils 真 0。本批补 scan/router/migrations 三项高 ROI·ai_tools 低价值跳过·前端 utils 需核 vitest 配置另议。

  • 待修项回流 todo: 🔴/🟡


CR-260618-21 ai_tools 补 6 sync test + risk_level 前端核验(主代串行·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代串行 + cargo test 验证): PASS

  • 验证: cargo test -p df-ai ai_tools::tests 6 passed(112 filtered·现有测试零冲突)。

  • ai_tools.rs +6 sync test(零行为变更·只加 #[cfg(test)] mod):

    • risk_level_serde_lowercase(Low/Medium/High→"low"/"medium"/"high"·serde rename_all 验证)
    • register_and_get_tool(注册后可查·不存在返 None·len/is_empty)
    • register_same_name_overwrites(同名二次注册 HashMap insert 覆盖语义·后注册 risk_level 胜出)
    • tool_definitions_and_names(注册后 definitions/names 列表正确)
    • default_is_empty(Default trait 空注册表)
    • object_schema_collects_required(required 字段收集·is_required true 入 required)
  • risk_level 前端核验:src/api/types.ts grep risk_level 0 命中 → 后端 PendingApproval.risk_level(mod.rs:410)经 IPC 序列化但前端不消费 → 真死(坐实 SW-260618-21 risk_level 项·佐证选项 a 清理可行)。注:diff 活跃(:258 AiApprovalRequired + :328 ToolResult·UX-260618-06)非死。

  • 待修项回流 todo: 🔴/🟡


CR-260618-22 PendingApproval.risk_level 死字段删(主代串行·多角度分析确定推进·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代串行 + cargo check 验证 + 独立 grep 核验): PASS
  • 验证: cargo check --workspace EXIT 0(PendingApproval.risk_level dead_code warning 消失·剩 diff + releases/node_executions 基线)。

多角度分析·确定推进:

  • 字段死坐实:前端 types.ts risk_level 0 命中(前端不消费)·后端 cargo dead_code 报 never read·0 读取消费者。
  • 删波及可控:recover_pending 过滤语义保留(改 if risk_from_str().is_none(){continue}·仍跳损坏记录)·process_tool_calls 删赋值(risk_level 变量他处分流仍用)·零行为变更。

改动:

  • mod.rs:410pub risk_level: RiskLevel 字段 + :45use df_ai::ai_tools::RiskLevel import(删字段后 unused)。
  • audit.rs:282 recover_pending 删 risk_level: risk 赋值 + :275 过滤改 if risk_from_str(&rec.risk_level).is_none() { continue }(语义保留)。
  • audit.rs:671 process_tool_calls 删 risk_level, 赋值(risk_level 变量他处分流仍用)。

主代 grep 核验(防污染):

  • AuditRecord.risk_level(audit.rs:71·:115 序列化返 AuditLog)活跃保留。

  • audit 流程 risk_level(audit_tool_call:158/309 / build_approval_reason / trust_hits:596-740 分流 Low/Med/High)全活跃保留。

  • PendingApproval 构造的 risk_level 赋值(:282/:671)全删·grep 确认。

  • 待修项回流 todo: 🔴/🟡


CR-260618-23 SW-260618-21 b 预留功能标 allow 消 dead_code warning(主代串行·自主决策 b·工作区未提交·commit-policy-deferred) — 已审(PASS·主代自测)

  • 结论(2026-06-18·主代串行 + cargo check 验证): PASS·cargo check --workspace EXIT 0 0 warning(原 4 dead_code 全消)。

  • 自主决策 b(保留+标 allow):5 处预留/IPC 字段标 #[allow(dead_code)] + 注释预留意图·零波及消 warning:

    • SessionState enum(mod.rs:172)+ session_state fn(:393):预留读视图(SW-02 类终态化复用·0 消费者保留扩展点)
    • AppState.releases(state.rs:206)/node_executions(:210):预留 release/node exec Repo(持久化已就位·IPC 未接入)
    • PendingApproval.diff(mod.rs:417):IPC 活跃(useAiEvents:252+ToolCard·UX-260618-06)·cargo Rust never-read 误报
  • 多角度分析:risk_level 真死删(CR-22)·其余预留/IPC 活跃标 allow 保留(清理失扩展点 ROI 低·b 零波及消 warning 最优)。

  • 待修项回流 todo: 🔴/🟡


CR-260618-24 God拆分a(tool_registry file+data抽出+基线测试) + sweep批1-4死依赖/死代码/死导出/注释/i18n/model_fetch(workflow w2xkw4ybh data抽+主代file抽+主代sweep实施·工作区未提交·commit-policy-deferred) — 已审(ISSUES·🔴1🟡1·review 会话 3 agent 并行核验)

  • 范围:
    • God 拆分 a(SMELL-P0-2):tool_registry.rs build_ai_tool_registry 1091→7 行·抽 register_data_tools(18 持 db 工具·workflow w2xkw4ybh)+ register_file_tools(10 文件工具·主代)·加基线测试 test_build_ai_tool_registry_baseline_tool_count(Database::open_in_memory·断言 len()==28 + tool_names() 集合锁定)。
    • 批1 死依赖/死代码(Rust):df-execute/Cargo.toml 删 df-types/async-trait/tracing(全仓零引用) · df-workflow/Cargo.toml 删 thiserror(零引用) · df-workflow/eventbus.rsemit_human_approval_request(零调用·实际走 send())+ SendError import · df-ai/ai_tools.rsToolExecutionResult(零消费者)。
    • 批2 前端:useMarkdown.tsclearHistoryCache(零消费) · useConfirm.tsConfirmFn/AnswerFn(零 import) · useAiPanel.ts restoreUiStateexport(零外部调用) · markdown.ts stripMd 围栏正则 [a-zA-Z]*[^\n]*(吃任意 infostring 如 rust,norun) · time.ts formatDatei18n.global.locale(原硬编码 zh-CN·CR-260615-08 漏修)。
    • 批3 注释:df-project/manager.rs 删误导 TODO(分层正确·持久化在 storage 层) · df-ideas/promotion.rs 精简模块注释(历史空壳已删)。
    • 批4 med:df-ai/model_fetch.rs is_non_chat_model|| id.contains("-search")(误剔合法 search-augmented chat 模型 gpt-4o-search-preview·davinci-search 仍覆盖旧变种)+ 补单测 is_non_chat_model_keeps_search_augmented_chat
  • 验证:cargo check --workspace EXIT 0(0 warning) / cargo test df-ai 119 passed / cargo test -p devflow test_build_ai_tool_registry_baseline_tool_count 1 passed / vue-tsc EXIT 0。
  • 审查要点(供审查 agent):data/file 抽出零行为变更(28 工具基线锁定·防注册丢失) · 死依赖/死代码主代独立 grep 核验零引用 · model_fetch -search 删有单测锁回归 · formatDate i18n 行为变更(en locale 日期格式·方向对齐 CR-260615-08)。
  • 关联:SMELL-P0-2 销账 / ARC-260618-01 a 已实施 / ARC-d(conditions)/e(adversarial)/SW-21(formatRelativeZh)/22(useAiSend DRY) 进 todo。
  • 复审结论(2026-06-18·review 会话 3 agent 并行独立核验,不信主代声明): ⚠️ ISSUES🔴1 🟡1 1
    • God 拆分 PASS(Agent A):build 调 register_data_tools(:367)+ register_file_tools(:368),build 体零残留内联;重名注册闭环(巡检 #11 信号消除:28 name 静态互异 + 基线双断言 len==28 + sorted 集合防护 HashMap 覆盖);抽样 run_command/list_projects 逐字一致(get_*_count 仅位置移动非行为变更)
    • 批1 死代码全 PASS(Agent B):df-execute 删 df-types/async-trait/tracing + df-workflow 删 thiserror + eventbus 删 emit_human_approval_request/SendError + ai_tools 删 ToolExecutionResult,4 项全仓 grep 零引用(人类审批走 bus.send() 非删的 emit),无误删活引用
    • 批4 model_fetch PASS(Agent C):删 || id.contains("-search") 行为变更正确(search-augmented chat 保留 + davinci-search 旧变种仍剔),单测 25 测全过
    • 🔴 time.ts:44 formatDate 漏 .value(Agent C 铁证):legacy:false composition 模式 i18n.global.locale 是 ref,(i18n as any).global.locale === 'en' 比对象===字符串恒 false → en locale 永不生效,formatDate i18n 化失效(对齐 CR-260615-08 未完成回归)。App.vue:227/GeneralPanel:259 均用 .value 印证。修法:补 .value
    • 🟡 markdown.ts:19 stripMd 围栏正则边缘回归:[a-zA-Z]*[^\n]* 后,内联裸围栏无换行(See \``rust below`)吃到行尾。场景边缘(列表摘要极少内联裸围栏),建议注释标注边界
    • anthropic_compat.rs:433-436 编译错(precheck_messages/summarize_messages 未定义,应 Self::):非 CR-24 引入(工作区漂移),但致基线测试运行时未验证(Agent A 仅静态核验),主代核验稳定态后补跑 test_build_ai_tool_registry_baseline_tool_count
  • 待修项回流 todo: 🔴 1 项(UX-260618-16 time.ts formatDate 补 .value)

第二位审查 agent 独立核验印证(2026-06-18·独立 grep/read·防上下文污染):

  • 与 review 会话 3 agent 结论一致:🔴1(time.ts .value) / 🟡1(markdown.ts 边缘) / 1(anthropic_compat 漂移)
  • 🔴 time.ts:44 漏 .value 二次独立坐实:i18n/index.ts:17 legacy:false(composition 模式)→ i18n.global.localeWritableComputedRef 非 string。App.vue:227 + GeneralPanel.vue:259 均用 i18n.global.locale.value = ... 印证 ref 语义;(i18n as any).global.locale === 'en' 比较 ref 对象与字符串 恒 false → en locale 永不生效。本审查 agent 首轮未捕捉此对抗点,经 review 会话坐实后独立复核确认——印证对抗思维不可省(.value 是 vue-i18n legacy/composition 模式高频易错点)。
  • A. God 拆分独立佐证(超出对方会话覆盖):grep df-ai 全 crate #[(tokio::)?test] = 119 精确匹配主代报告(ai_tools 6 / router 15 / context 23 / anthropic_compat 12 / openai_compat 10 / model_fetch 25 / retry 6 / model_probe 22);build_ai_tool_registry:365-370 体 0 registry.register(全抽出);register_data_tools(:380-761)18 处 register / register_file_tools(:765-1471)10 处 register,与 CR-24 自述「18+10=28」逐一对齐;run_workflow:610 闭包无 let db = db.clone() 前缀(对比其余 17 data 工具全有),CR-52 防御 Err 设计保留;RiskLevel 28 处计数与 28 工具一一对应。
  • B. 批1 死代码独立佐证:df-execute/Cargo.toml:6-10 仅剩 serde/serde_json/tokio/anyhow(workspace 4 个),src/ 零 df_types/async_trait/tracing import,无跨 crate 间接引用(workspace 依赖图安全);df-workflow/Cargo.toml 当前依赖含 df-types/async-trait/tracing(CR-24 仅声明删 thiserror,其余正常持有);eventbus.rs:1-56 仅 send/subscribe/with_capacity/Default/Clone;ai_tools.rs ToolExecutionResult 全仓零引用。
  • C. 批2 前端独立佐证:clearHistoryCache/ConfirmFn/AnswerFn 全 src 零命中(仅待审查.md 自述);useAiPanel.ts:49 function restoreUiState() 无 export,:52 模块内自调;markdown.ts:19 /```[^\n]*\n?/g(非 [a-zA-Z]*)印证。
  • D. 批3 注释独立佐证:manager.rs grep TODO 零命中,create() doc :46-47「构造领域实体(不落库);持久化由调用方经 storage 层 ProjectRecord 映射完成」;promotion.rs:1-3 模块注释 3 行精简。
  • E. 批4 med 独立佐证:model_fetch.rs:196-219 is_non_chat_model 列表(图片/语音/realtime/transcribe/moderation/:218 davinci-search),-search 残留;新测试 :281-289 4 断言(3 search-augmented 保留 + 1 davinci-search 仍剔);另有 :394-396 non_chat_search_variants_filtered 印证。
  • 🟡 markdown.ts:19 严重度重评:本审查 agent 倾向判 low 而非 med——内联裸围栏(See \``rust below无换行)场景在列表摘要极罕见(MD 代码块必带换行,内联裸围栏本身是非规范写法),影响范围窄。对方会话判 med 偏保守,差异在严重度估计非事实分歧。建议修法:加[^\n]*` 边界注释标注「内联裸围栏场景」即可,无需改正则。
  • anthropic_compat.rs:433-436 编译错印证:确认非 CR-24 引入(工作区漂移),需主代核验稳定态后补跑基线测试(对方已记录)。

主代修复闭环(2026-06-18): 🔴 time.ts:44 已补 .value((i18n as any).global.locale.value·对齐 App.vue:227)·vue-tsc EXIT 0 印证 en locale 现正确生效。CR-24 🔴 消·verdict 转 PASS(🟡 markdown.ts:19 边界注释 low 可选· anthropic_compat 漂移非本 CR 待独立核验)。


CR-260618-25 wo7d6zdqm findings 推进(secret keyring provider id + shell 超时命令名 + run_command timeout clamp 600s) + wdvtbjozg ux(异步按钮禁用态防双击 + 空态/错误态·7 vue + i18n)(workflow 多代理 + 主代实施·工作区未提交·commit-policy-deferred) — 已审(ISSUES·主代自测 PASS / review 复审抓出 🟡1·handleApprovalMulti 漏 submitting)

  • 范围:
    • wo7d6zdqm 错误消息/安全(主代实施):df-storage/secret.rs keyring 3 处错误带 provider id(entry_for/set/delete) · df-execute/shell.rs:102 超时消息带命令名 · tool_registry.rs run_command timeout_secs clamp 600s(防 LLM 传超大冻结) · utils/time.ts formatDate.value(CR-24 🔴 修复)。
    • wdvtbjozg ux(workflow 3 agent·对齐 ProviderPanel/Tasks 范式):异步按钮禁用态(submitting ref try/finally)防双击——Ideas confirmCapture/promote/delete · Projects submitCreate · Knowledge saveEdit/reject/archive/publish/submitCreate · Tasks confirmCreate(+空标题禁用) · ProjectDetail submitNewTask/handleApproval · ConnectionPanel saveConn · Dashboard refresh;三态/错误态——Ideas 列表 loading/error/空 · Projects error-banner · Dashboard loading/errorMsg + loadAll 抽函数;i18n(zh/en ideas.ts listEmpty/creating/deleting/promoting · dashboard.ts err.loadFailed)。
  • 验证:cargo check -p devflow -p df-storage -p df-execute EXIT 0 / vue-tsc EXIT 0。
  • 审查要点(供审查 agent):secret/shell 错误消息改进零行为变更(仅文案带 id/command) · run_command timeout clamp 行为变更(>600s 静默 clamp·防冻结·合理) · time.ts .value 是 CR-24 🔴 修复(en locale 现生效) · ux 禁用态/三态对齐范式(各 submitting ref try/finally 复位·空态条件顺序 loading→error→空→列表) · 独立 grep 核验各 vue submitting ref 单声明 + 模板 :disabled 联动。
  • 关联:wo7d6zdqm 多维度 sweep(性能 agent 速率失败·errmsg/ux/security 3 agent 完成) + wdvtbjozg ux workflow + CR-24 🔴 time.ts 修。
  • 待修项回流 todo: 无(主代自审)。⚠️ review 复审(2026-06-19·2 agent 并行·独立 grep/read 核验)推翻主代 PASS:🟡1 ProjectDetail.handleApprovalMulti 漏 submitting(ProjectDetail.vue:431-437 无 set true/finally + 模板 :198 只绑 multiDecisions.length → 多选审批 IPC 进行中按钮不禁用 = 防双击破口;对比 handleApproval :417-425 正确 try/finally,handleApprovalMulti 漏对齐)。已回流 todo UX-260618-17。3(timeout clamp 静默/timeout_secs:0 边界/shell.rs:102 注释防回归)低优先可选。

复审结论(2026-06-19·独立 grep 核验源码当前形态): PASS · 🔴0 🟡0 2(low·可选)。verdict 转 PASS。主代自测(cargo check EXIT 0 / vue-tsc EXIT 0)与独立源码核验一致,无 high/med。

逐项核验表(file:line 佐证 + 判定):

核验点 佐证 判定
A-secret 3 处 keyring 错误带 provider id secret.rs:92 entry_for (provider={})+id · :115 set (provider={})+id · :121 delete (provider={})+id
A-shell 超时 map_err 带命令名 + secs shell.rs:102 命令执行超时({}s): {}secs+request.command(ShellRequest 字段在作用域内)
A-clamp timeout_secs .min(MAX) + 常量 600 tool_registry.rs:26 MAX_RUN_COMMAND_TIMEOUT_SECS: u64 = 600 · :792 .unwrap_or(DEFAULT=60).min(MAX) clamp · :24 DEFAULT=60(默认 60<600·clamp 不影响正常超时·仅封顶 LLM 传超大冻结)
A-time formatDate .value(CR-24 🔴) time.ts:44 (i18n as any).global.locale.value === 'en' · 对齐 App.vue:227 i18n.global.locale.value = savedLang(en locale 现正确生效)
B-Ideas 三态条件顺序 + store 字段 Ideas.vue:37-39 v-if store.loadingv-else-if store.errorv-else-if filteredIdeas.length===0v-else 列表 · 顺序正确 · :236 useProjectStore · project.ts:90-91,93 store.loading/error/clearError 均存在(getter 直读 state)
B-Projects error-banner + clearError + submitting Projects.vue:13-16 v-if store.error + dismiss store.clearError() · :51 :disabled="submitting || !newName.trim()"
B-Dashboard loading ref + errorMsg + loadAll + refresh :disabled Dashboard.vue:124-125 loading/errorMsg ref · :194-206 loadAll() 抽函数(try/finally+guard if(loading) return) · :10 refresh :disabled="loading" · :21-24 error-banner
C-Ideas creating/deleting/promoting 单声明 + try/finally + :disabled Ideas.vue:253-255 三 ref 各一次声明 · :402/416/428 入口 guard(|| xxx.value) · :404-412/419-424/430-439 try/finally 复位 · :176/181/207 :disabled 联动
C-Projects submitting 单声明 + try/finally + :disabled + guard Projects.vue:225 单声明 · :278 guard if(submitting) return(在 :279 submitting=true 前) · :279-290 try/finally · :51 :disabled
C-Knowledge submitting 单声明 + 5 函数 try/finally + :disabled Knowledge.vue:353 单声明 · saveEdit/publishCurrent/rejectCurrent/archiveCurrent/submitCreate 均 try/finally 复位 · :99/100/102/106/233 :disabled 联动
C-Tasks submitting 单声明 + 空标题 guard + try/finally Tasks.vue:137 单声明 · :222 guard !newTaskTitle.trim():223 submitting=true 前 · :223-236 try/finally · :103 :disabled 联动 · 三态 loading/error/empty :43-45
C-ProjectDetail submitting 单声明 + submitNewTask/handleApproval try/finally + :disabled ProjectDetail.vue:324 单声明 · :327 guard trim · :328-343 submitNewTask try/finally · :418-424 handleApproval try/finally · :36/211 :disabled(审批选项按钮防双击换选项)
C-ConnectionPanel submitting 单声明 + saveConn + :disabled ConnectionPanel.vue:110 单声明 · :157-177 try/finally · :63 :disabled (low 见下)
D-i18n-zh ideas listEmpty/creating/deleting/promoting + dashboard err.loadFailed zh-CN/ideas.ts:20-23 · zh-CN/dashboard.ts:37 loadFailed: '加载数据失败,请稍后重试'
D-i18n-en 对称 en/ideas.ts:20-23 · en/dashboard.ts:37 loadFailed: 'Failed to load data, please retry later'

对抗核验印证:

  • submitting ref 单声明? 7 vue 全部 setup 内单声明(无重复 declare):Ideas :253-255 三 ref·Projects :225·Dashboard :124·Knowledge :353·Tasks :137·ProjectDetail :324·ConnectionPanel :110。无重复声明。
  • try/finally 复位? 所有异步函数均 try/finally 复位(异常路径不卡死)。无漏 finally。
  • store 字段存在? project store(project.ts:90-93)getter 暴露 loading/error/clearError;Ideas/Projects/Tasks/Dashboard 用 useProjectStore;Knowledge 用 useKnowledgeStore(:249)+ 本地 clearError wrapper(:302-303 调 store.clearError)。字段均存在、store 用对。
  • 行为变更标注? 仅 run_command clamp 为静默行为变更(>600s 截断·防 LLM 冻结·合理·注释 :791 标注),secret/shell/time.ts 均零行为变更(纯文案/纯 bug 修)。time.ts .value 是 CR-24 🔴 必要修复(en locale 原本恒中文·回归)。
  • error-banner 重复? Ideas/Projects/Dashboard/Knowledge 各自 error-banner(消费各自 store.error / 本地 errorMsg),非同一 store·非冲突·对齐 Knowledge 范式。

low(可选·非必修):

  1. Knowledge.vue submitCreate(:438)/ ProjectDetail.vue handleApproval(:417) 缺显式 if(submitting) return 入口 guard(其余 5 处有)。实际防并发由模板 :disabled="submitting" 兜住(按钮点不动·无重入),IPC 路径单调用,风险极低。可选补 guard 对齐范式一致性。
  2. ConnectionPanel.vue saveConn(:152) 是同步函数(无 await),submitting try/finally 同步置位即复位,此处 submitting 实际无防双击语义(纯占位/视觉)。无功能影响,可选移除或改 async。

CR-260619-01 UX-14 advance_task渲染缺任务名 + UX-16 time.ts销账 + UX-17 handleApprovalMulti防双击(workflow wwchro468 UX-14实施 + 主代串行 UX-16/17·主代兜底核验) — 已审(PASS·巡检 2026-06-19 独立 grep/read 核验)

  • 范围:
    • UX-260618-14 advance_task 渲染缺任务名(workflow wwchro468 general-purpose agent 实施 6 文件):前端 ToolCard.vue advance_task 渲染取 r.title||r.id(:474 formatToolResult / :891 toolResultSummary)+ 新增 TASK_ID_TOOL_ARG 映射(:799 advance_task→id / run_workflow→task_id)+ displayArgValue 接入 task_id→title(:834)+ i18n zh/en aiTool.ts(advancedTask/advancedTaskWithRounds 改 {title} + taskLabel/taskIdNotFound);后端 audit.rs 新增 resolve_task_label(查 tasks 表 :151 三臂 Ok(Some)/Ok(None)/Err 对齐 resolve_project_label)+ build_approval_reason advance_task 分支(:173 id 走 task 解析)+ tool_registry.rstool_display_hint 的 advance_task 项(:1575 让 :1549 模板「推进任务状态:{} → {}」生效)。
    • UX-260618-16 time.ts .value 销账:主代核验已修(CR-24/25 闭环),漏销账补登。
    • UX-260618-17 handleApprovalMulti 补 submitting(主代串行):ProjectDetail.vue :431 try/finally + 模板 :198 :disabled="submitting || multiDecisions.length===0" 对齐单选 handleApproval。
  • 验证(主代兜底,不信 agent 自报):cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证 resolve_task_label 三臂 / tool_display_hint 删 advance_task 项 / ToolCard title 取值。
  • 审查要点(供审查 agent):① UX-14 行为变更——审批 reason 文案(advance_task 从静态串「推进任务状态」→ 含任务标题+target_status)+ audit resolve 查 tasks 表(原误查 projects 恒报「项目已不存在」);② resolve_task_label 三臂语义对齐 resolve_project_label(空 id/Ok(None)/Err 分流);③ tool_display_hint 删 advance_task 后 run_workflow 保留轻量命中(模板 task_id 走通用裸显,本批不动);④ 跨项目任务 taskNameById 查 projectStore.tasks 查不到降级「任务不在当前列表」(非硬错);⑤ UX-17 handleApprovalMulti submitting 对齐 handleApproval 防双击。
  • 关联:todo UX-260618-14/16/17 销账。

复审结论(2026-06-19·巡检独立 grep/read 核验当前源码形态·commit 1dc8b12 已落地·不信声明): PASS🔴0 🟡0 1

逐项核验表(file:line 佐证 + 判定):

核验点 佐证 判定
A-audit resolve_task_label 三臂对齐 resolve_project_label audit.rs:151-163:空 id→空串 / Ok(Some)→「标题」(id=x) / Ok(None)→任务已不存在 / Err→warn+裸 id。与 resolve_project_label:131-144 同构
A-build_reason advance_task 的 id 特化走 task 表 audit.rs:194 "id" if tool_name=="advance_task" => resolve_task_label(db,&val)。其余 "id"|"project_id" 仍走 resolve_project_label(:195)
A-tool_registry 删 tool_display_hint advance_task 轻量命中 tool_registry.rs:1575-1576 注释「advance_task 不在此轻量映射」;:1549 display_hint_for_tool 模板「推进任务状态:{id}→{target_status}」生效
A-TaskRecord 返回值含 title(非假修复) df-storage/models.rs:54-57 TaskRecord pub title:String;task_advance_node.rs:43-47 advance_task_atomic→Result;serde 默认序列化 title,前端 r.title 可读
B-formatToolResult advance_task 优先显 title 回退 id ToolCard.vue:474-478 formatToolResult r.title||r.id;review_rounds>0 走 advancedTaskWithRounds,=0 走 advancedTask
B-toolResultSummary header 摘要与 body 对齐 ToolCard.vue:921-924 同 formatToolResult 双路径覆盖
B-displayArgValue task_id→title 无 project 冲突 ToolCard.vue:824 TASK_ID_TOOL_ARG{advance_task:'id',run_workflow:'task_id'};:811-815 PROJECT_ID_TOOL_ARG 不含 advance_task → :835 projectArgKey 为 undefined 跳过 → :845 taskArgKey 命中;:806 taskNameById 查 projectStore.tasks 降级 taskIdNotFound
B-i18n 中英对称 + message 无特殊字符 zh-CN/aiTool.ts:63-104 + en/aiTool.ts:62-102 advancedTask/advancedTaskWithRounds/taskLabel/taskIdNotFound/workflowTriggered 全对称;message 仅 {title}{status}{n}{id}{exec} 标准插值,无 @//裸 {}(prod runtime 盲点安全)
C-time formatDate .value(CR-24/25 闭环) time.ts:44 (i18n as any).global.locale.value === 'en'。composition 模式 ref 读 .value,en locale 现生效
D-ProjectDetail handleApprovalMulti submitting 防双击 ProjectDetail.vue:431-440 async + try/finally(submitting=true :433 / finally=false :440);模板 :198 :disabled="submitting||multiDecisions.length===0" 联动;对齐单选 handleApproval :417-425

对抗核验印证:

  • run_workflow 审批 reason 不含任务名? 非遗漏:tool_display_hint:1577 run_workflow 轻量命中「触发工作流」(动词级,在 build_approval_reason:184 优先于 display_hint_for_tool 模板返回),根本不进模板分支,故 task_id 无裸显问题。review 要点③已声明「run_workflow 本批不动」,advance_task 是 UX-14 唯一范围。

  • advance_task(Medium risk)是否走审批触发 build_approval_reason? Medium 既有审批阈值逻辑非本批改动;无论审批卡触发与否,前端 formatToolResult/toolResultSummary(执行结果渲染)双路径均消费 title,UX-14 修复在审批卡 + 结果卡两处均生效。

  • i18n message prod 编译盲点? memory i18n-message-compile-blindspot 教训落实:grep message 值全部标准 {var} 插值,无字面 @//|/裸 {}

  • PROJECT_ID_TOOL_ARG / advance_task key 冲突? advance_task 不在 PROJECT_ID_TOOL_ARG(:811-815 仅 4 个 *_project),displayArgValue 项目分支短路跳过,无 advance_task.id 被误判为 project_id 风险。

  • WATCH-1: run_workflow 审批 reason 走轻量「触发工作流」动词(不含 task 名/target_status),UX-14 范围外;若后续需 run_workflow reason 显任务名,需同时从 tool_display_hint :1577 移除 run_workflow(让 display_hint_for_tool :1550 模板生效)+ build_approval_reason task_id key 特化走 resolve_task_label。当前接受动词级。

  • 待修项回流 todo: 🔴/🟡

CR-260619-02 UX-260618-15 第一批 流式重试N+1气泡根治·方案A(agent ux15-batch1 实施 + 主代兜底核验 + 注释修正) — 已审(PASS·0)

  • 范围(3 文件 + 主代注释修正):
    • stream_recv.rs:InitFailed 变体加 error: String 字段(:125);5 处重试路径 AiError emit 全移除(idle timeout :203 / stream-error :262 / chunk Err :303 / 流尽无 finished :362 / provider.stream() Err :402),retryable=true 重试路径 emit 权交 agentic,retryable=false Fatal 也交 agentic 统一 emit。主代修正注释残留 :251/:299(旧"emit AiError"→"返回 InitFailed")。
    • agentic.rs:重试 loop 解构 InitFailed{retryable,error};Fatal 分支(:726)+ 全 candidate 耗尽(:748)新增 emit AiError(单气泡聚合,Network 分类);保留 ensure_resolved_key Auth(:401)/压缩失败(:618)/try_continue provider 缺失(:1058)emit。
    • useAiEvents.ts:AiStreamRetry(:177)改首次创建+后续更新(末条非 isError 创建新错误气泡 / 是 isError 更新 content);AiError push 保留(耗尽/Fatal 最终)。
  • 验证(主代兜底,不信 agent 自报):cargo check -p devflow EXIT 0 + cargo test -p df-ai 119 passed + vue-tsc EXIT 0 + grep 核验 5 处 emit 移除判定 + 注释修正。
  • 审查要点(供审查 agent):① N+1 根治——重试过程不再每轮 push AiError,改 AiStreamRetry 单气泡(首次创建+更新),耗尽/Fatal emit 1 最终 AiError(N+1→1重试+1最终);② stream_recv 5 处移除判定(retryable=true 重试路径移除 / Fatal 交 agentic);③ agentic Fatal/耗尽 emit 单气泡(Network 分类);④ riskNote error_type 统一 Network——401 鉴权 Fatal 原本 error_type=None(前端 canOpenSettings 只认 {auth,provider_config} 本不显「去设置」),现 Network 也不显,不恶化既有行为(记 low WATCH:理想 401 显 auth 按钮需单独做);⑤ InitFailed 枚举加 error 字段 pub(crate) 内部无破坏。
  • 关联:todo UX-260618-15 第一批销账;第二批(方案C 聚合气泡显示错误原因)可选增强待定。

复审结论(CR-260619-02): verdict PASS(0 high / 0 med / 0 low)。独立 grep/read 核验 3 文件当前形态,N+1 气泡根治落地完整,emit 权移交无 Fatal swallow,单气泡聚合无重复 emit,保留 emit 未误删,注释与代码一致。

逐项核验表(源码 file:line 佐证):

审查点 核验结论 佐证
N+1 根治:重试不再每轮 push AiError PASS stream_recv.rs 全文 emit 仅 AiTextDelta(:229)/AiHeartbeat(:329);5 处 InitFailed 路径(:206/:265/:306/:364/:407)均 return StreamResult::InitFailed{...} 不 emit
5 处移除完整无遗漏 InitFailed emit PASS grep AiError 在 stream_recv.rs 仅命中注释/Doc,无运行时 emit 调用
retryable=true 重试路径移除判定正确 PASS agentic.rs:243-300 解构 InitFailed{retryable,error},retryable=true 走重试 emit AiStreamRetry(:293);retryable=false Fatal 返回 StreamOutcome::Fatal{error}(:254)
retryable=false Fatal 正确交 agentic(无 swallow) PASS 外层 agentic.rs:735-744 StreamOutcome::Fatal 分支 guard.reset + emit AiError(Network) + return,终态单 emit
Fatal/耗尽单气泡聚合无重复 emit PASS Fatal 分支 :739 单 emit;全 candidate 耗尽 :761 单 emit;last_exhausted_error 累积 Exhaused 文本(:688 init / :732 write),Exhausted 分支 :724 注释「不在此 emit」防残留
保留 emit 未误删 PASS ensure_resolved_key Auth :401 / 压缩失败 Unknown :618 / try_continue ProviderConfig :1058 三处 emit 完整
useAiEvents AiStreamRetry 首次创建边界 PASS useAiEvents.ts:185 if (lastMsg && lastMsg.isError) 更新 content else push 新错误气泡;重试过程不再先 emit AiError 故首条到达末条非 isError,正确创建;后续 isError 更新
401 鉴权走 ensure_resolved_key(Auth)非 stream_llm Fatal(Network) PASS 401 key 缺失/损坏早失败在 agentic.rs:390 ensure_resolved_key Err → :401 emit Auth;stream_llm Fatal(Network) 仅覆盖 key 已过 ensure 但被 provider 拒等场景
注释 :251/:299 修正一致 PASS stream_recv.rs :203-204/:262-263/:303-305/:362-363/:402-404 均修正为「不再 emit AiError/返回 InitFailed」,与代码行为一致

对抗印证:① stream_recv.rs 无运行时 AiError emit(grep 仅命中注释/Doc);② agentic.rs AiStreamRetry 单点 emit(:293)位于 retryable 重试分支,Fatal/Exhaused 分支均不重复;③ InitFailed 枚举字段 error:String(:125)pub(crate) 内部,跨模块解构(agentic.rs:243)正确。

WATCH:

  • WATCH-1(沿用 riskNote ④,low):stream_llm Fatal 路径 error_type 统一 Network,401 经此路径时不显「去设置」按钮(前端 canOpenSettings 仅认 {auth,provider_config})。401 主路径已由 ensure_resolved_key Auth 兜底,不恶化既有行为;理想 401 显 auth 按钮需单独做。

待修项回流 todo: 🔴/🟡

CR-260619-03 SMELL-P1-9 crud.rs按表拆分 + F-09批1 PerConvState(agent crud-split + f09-batch1 实施·主代统一兜底核验) — 已审(PASS·🟡1 WATCH·批2中间态)

  • 范围(2 独立任务攒批):
    • SMELL-P1-9 crud.rs 拆分(agent crud-split):crud.rs 2212 行 → crud/ 6 文件(mod.rs 宏+工具+re-export / settings / project_repo / task_repo / conversation_repo / idea_repo)。12 Repo + from_row 按表域归属;re-export pub use {conversation,idea,project,settings,task}_repo::* 零调用方改动;宏 pub(crate) use impl_repo + 子模块 use super::impl_repo;基线测试 all_known_tables_have_column_whitelist(12 表)+ all_repos_constructible_in_memory(13 Repo new 不 panic)。
    • F-09 批1 PerConvState(agent f09-batch1,主代已自主裁决采纳决策 b-1):commands/ai/mod.rs 新增 PerConvState struct(9 字段,初值逐字对齐 AiSession::new)+ AiSession.per_conv HashMap 字段(共存期顶层字段仍是真相源)+ conv()(惰性创建)/conv_read()(只读)访问器 + 3 单测。纯新增无行为变化,批2 迁移承接。
  • 验证(主代统一兜底,稳定态·两 agent 都完成):cargo check --workspace EXIT 0 + df-storage 35 passed+11 integration + devflow 96 passed(原93+F-09批1新3)。
  • 审查要点(供审查 agent):① SMELL re-export 完整性(全仓 df_storage::crud::XxxRepo 路径不变,cargo --workspace 0 error 印证);② 宏可见性(pub(crate) use + 子模块 use super + helper import);③ 12 Repo/from_row 归属表正确(idea/project/task/conversation/settings 域);④ 基线测试锁回归(12 表白名单 + 13 Repo 构造);⑤ F-09 批1 PerConvState 字段初值对齐 AiSession::new + conv/conv_read 逻辑 + #[allow(dead_code)] 共存期标注合理(批2 迁移后移除);⑥ F-09 批1 纯新增无行为变化(顶层字段未迁移)。
  • 关联:todo SMELL-P1-9 销账 / F-09 阶段2 批1(批2-8 待)。

复审结论(2026-06-19·独立 grep/read 核验源码当前形态·commit 2c8764a 已落地·不跑 cargo 避批2中间态误报): PASS🔴0 🟡0 1

逐项核验表(file:line 佐证 + 判定):

核验点 佐证 判定
A-拆分 crud.rs 2212 行 → 6 文件 crates/df-storage/src/crud/ 实存 mod.rs/settings.rs/project_repo.rs/task_repo.rs/conversation_repo.rs/idea_repo.rs 6 文件;旧 crud.rs 已删(git show 2c8764a -2212)
A-re-export pub use {conversation,idea,project,settings,task}_repo::* 5 路全在 mod.rs:21-25 五条 pub use 全部就位;调用方 df_storage::crud::{TaskRepo,ProjectRepo,IdeaRepo,AiProviderRepo,AiConversationRepo,AiToolExecutionRepo,KnowledgeRepo,KnowledgeEventsRepo,KnowledgeEventsRepo,WorkflowRepo,...} 路径 grep 全部命中且未改(state.rs:13 / workflow.rs:15 / audit.rs:11 / conversation.rs:7 / title.rs:15 / knowledge_inject.rs:15 / knowledge_timeline.rs:11 / agentic.rs:352 / tool_registry.rs 多处)
A-宏可见性 pub(crate) use impl_repo + 子模块 use super::impl_repo mod.rs:191 pub(crate) use impl_repo(宏定义 :40-187 在前·textual scope 注入注释 :189-190 说明顺序约束);子模块 import 全在:project_repo.rs:15/task_repo.rs:13/conversation_repo.rs:13/idea_repo.rs:13use super::impl_repo
A-helper import 子模块用 super::{now_millis_str,storage_err,validate_column_name,...} project_repo.rs:16 use super::{normalize_stored_path, now_millis_str, storage_err, validate_column_name} / task_repo.rs:14 / conversation_repo.rs:14 / idea_repo.rs:14;settings.rs:12 use super::{now_millis_str, storage_err}(KV 不需 validate/Arc/Mutex 外借,自含);mod.rs 工具 storage_err:199/normalize_stored_path:206/now_millis_str:220 全 pub(crate) 可见
A-12Repo归属 13 Repo 按 5 域正确分布 settings.rs:22 SettingsRepo(1,手写 KV)·project_repo.rs 5(ProjectRepo:100/BranchRepo:256/ReleaseRepo:283/WorkflowRepo:310/NodeExecutionRepo:337)·task_repo.rs 1(TaskRepo:45)·conversation_repo.rs 3(AiProviderRepo:114/AiConversationRepo:153/AiToolExecutionRepo:184)·idea_repo.rs 3(IdeaRepo:118/KnowledgeRepo:147/KnowledgeEventsRepo:400)=13 Repo 全在;from_row 12 个(宏生成 Repo 各一,SettingsRepo 无 from_row)归属域正确(project_from_row/branch/release/workflow/node_execution/task/ai_provider/ai_conversation/ai_tool_execution/idea/knowledge/knowledge_event)
A-基线测试 12 表白名单 + 13 Repo 构造 mod.rs:243-255 all_known_tables_have_column_whitelist 12 表全断言(ideas/projects/tasks/releases/branches/workflow_executions/node_executions/ai_providers/ai_conversations/ai_tool_executions/knowledges/knowledge_events)·mod.rs:259-276 all_repos_constructible_in_memory 13 Repo(SettingsRepo/IdeaRepo/ProjectRepo/TaskRepo/BranchRepo/ReleaseRepo/WorkflowRepo/NodeExecutionRepo/AiProviderRepo/AiConversationRepo/AiToolExecutionRepo/KnowledgeRepo/KnowledgeEventsRepo)逐 new 不 panic
A-白名单单一源 allowed_columns_for/validate_column_name/is_allowed_column 唯一在 settings.rs settings.rs:119 allowed_columns_for / :189 validate_column_name(pub(crate)) / :201 is_allowed_column(pub);调用方 df_storage::crud::is_allowed_column(tool_registry.rs:452/555)路径未破
B-PerConvState字段 9 字段逐字对齐 AiSession::new mod.rs:571-590 struct 9 字段;:605-617 new() 初值:messages(ContextManager::new default)=AiSession:386 / generating:false=:391 / stop_flag(Arc AtomicBool false)=:393 / notify(Arc Notify::new())=:394 / iteration_used:0=:395 / agent_language:None=:392 / model_override:None=:396 / session_trust(HashSet::new())=:397 / created_at:None(新增字段,注释 :604 对齐 AiSession.active_conv_created_at:None :389) 全对齐
B-conv访问器 entry().or_insert_with 惰性创建 mod.rs:453-457 conv(&mut self,conv_id) body self.per_conv.entry(conv_id.to_string()).or_insert_with(PerConvState::new) — 惰性创建语义正确,已存在返同一实例
B-conv_read访问器 .get() 只读不创建 mod.rs:466-468 conv_read(&self,conv_id) body self.per_conv.get(conv_id) — 返 Option<&PerConvState>,无写入路径,只读语义正确
B-allow标注 PerConvState/per_conv/conv/conv_read 共存期 #[allow(dead_code)] PerConvState struct:570 / per_conv 字段:355 / conv:452 / conv_read:465 四处均标 #[allow(dead_code)] 注释「F-09 B 批1 共存期:批2 迁移承接(0 调用方)」
B-3单测 new 初值 + 惰性创建复用 + conv_read 不创建 mod.rs:477 test_per_conv_state_new(8 断言含 stop_flag SeqCst load) / :497 test_conv_lazy_create(首次创建 len=1 + 同 id 指针相等复用 len 仍=1 + 不同 id 新建 len=2) / :525 test_conv_read_none(未创建返 None + 不触发创建 per_conv 仍空) 三测覆盖
B-纯新增 顶层字段仍是真相源,per_conv 初始空 AiSession::new:398 per_conv: HashMap::new()(初始空);批1 仅加 struct/字段/访问器,无调用方迁移(conv/conv_read 0 调用方) (见 WATCH·批2 中间态)

对抗核验印证:

  • re-export 路径不变? 全仓 grep df_storage::crud 命中 9 调用文件,所有 XxxRepo/is_allowed_column 路径形式未变,re-export 5 路全覆盖零调用方改动。
  • 宏 textual scope 顺序? mod.rs 宏定义(:40-187)在 pub(crate) use impl_repo(:191)之前,pub(crate) usemod 声明(:15-19)之前,顺序符合 Rust textual scope 注入要求(注释 :189-190 显式说明);4 子模块 use super::impl_repo 全部命中。
  • 13 Repo 计数? 实测 1(settings 手写)+5(project)+1(task)+3(conversation)+3(idea)=13,与基线测试 :263-275 逐行 new 一一对齐。
  • PerConvState created_at 新增字段是否破坏对齐? AiSession 无独立 created_at 顶层字段(用 active_conv_created_at:None :389),PerConvState.created_at:None :615 注释 :604 显式声明「批1 新增字段,AiSession 现有 active_conv_created_at 同语义」——对齐声明而非逐字复制,合理(语义等价,字段名差异因 AiSession 持 active 概念而 PerConvState 持会话创建时间)。
  • 批1 是否纯新增? AiSession::new 仍构造全部顶层字段(:386-397 messages/generating/agent_language/stop_flag/notify/iteration_used/model_override/session_trust),per_conv 初始空 HashMap,conv/conv_read 0 调用方——批1 范围严格纯新增。

⚠️ 批2 中间态说明(非批1 问题·审查范围外记录):

  • 核验时发现 mod.rs:355-380 顶层 8 字段已标 #[allow(dead_code)] 注释「F-09 B 批2 共存期死字段(批3 删)」,且 AiSession::new:386-397 仍构造这些字段——说明 F-09 批2 正在进行中(对应 prompt 开头「批2 agent 正在改 agentic.rs」)。批1 的 per_conv/conv/conv_read/PerConvState 已就位待批2 迁移调用方消费。本次审查范围(commit 2c8764a)仅含批1,批2 中间态不评判。本次审查全程未跑 cargo(memory review-batching-worktree-transient 教训:批2 中间态会致 check 快照误报,源码形态核验 > check 快照)。

  • WATCH-1: F-09 批2 迁移进行中(agentic.rs/commands.rs/audit.rs/conversation.rs/title.rs/knowledge_inject.rs 调用方迁移 + 顶层字段降级死字段),批2 完成后需独立审查(范围:调用方迁移完整性 + 顶层字段是否真成死字段 + cargo test 单会话全功能回归)。批1 本身 PASS 不受批2 影响。

  • 待修项回流 todo: 🔴/🟡 项(批1 范围内)

CR-260619-04 F-09批2 调用方迁移per-conv·双写桥接(agent f09-batch2·主代严格兜底·commit 2a4d745) — 已审(PASS·1 low·独立 grep/read 核验)

  • 范围(8 文件):agentic.rs(GeneratingGuard conv_id + 三处退出校验改 conv 存在性 !per_conv.contains_key 决策e + stop_flag/notify/messages/iteration per_conv + try_continue conv_id 参数)+ commands.rs(IPC 写路径双写桥接:per_conv 新真相源+顶层双写,批4删)+ audit/conversation/title/knowledge_inject/lib.rs(per_conv 读写迁移)+ mod.rs(conv/conv_read 去 allow)。
  • 共存期双写:per_conv 主 + 顶层双写(兼容批4前 IPC 读路径 ai_is_generating 读 per_conv fallback 顶层)。

复审结论(2026-06-19·独立 grep/read 核验源码当前形态·稳定态 commit d960417·cargo check 0 warning): PASS🔴0 🟡0 1

逐项核验表(file:line 佐证 + 判定):

核验点 佐证 判定
A-GeneratingGuard guard 持 conv_id + reset/Drop 双写 per_conv+顶层 agentic.rs:79-84 struct{session,conv_id,done} · :94-102 reset() 双写 session.conv(conv_id).generating=false + session.generating=false · :112-124 Drop spawn 双写 · :107-109 disarm() 审批等待 return 前置 done=true 跳过复位(B-260615-26 续生成修复保留)
B-三处退出校验 conv 存在性 !per_conv.contains_key 决策e agentic.rs:552(loop 入口切换检测) / :875(MidStream 保文前) / :926(stream 后 push 前)三处均 if !session.per_conv.contains_key(&conv_id) { warn + return },语义一致防 push 写回已删 conv 污染。prompt 声明 :541/864/917 略漂移,实为 :552/875/926(注释块行号差)
C-双写一致性 commands 写路径全双写 per_conv+顶层 commands.rs:227-265 ai_chat_send session.conv(&active).*(generating/stop_flag/iteration_used/model_override/messages)+ 同锁内 session.* 顶层双写 · :98-114 ai_regenerate · :760-777 ai_edit · :882-930 force_send 双写复位 old+顶层+占用 new · :1020-1045 ai_chat_stop 双写 stop_flag + :1042-1046 notify per_conv(else 顶层) · :1117-1160 ai_stop_loop 双写
D-读路径fallback ai_is_generating/各读 per_conv fallback 顶层 commands.rs:175-180 ai_is_generating session.conv_read(&active).map(c.generating).unwrap_or(session.generating) · :130 regenerate 读 messages fallback · :214/752 generating 拦截 fallback · audit/conversation/title/knowledge_inject 各 conv_read 后 unwrap_or(顶层)
E-conv_id上下文 loop 入参/IPC param/approval.conversation_id/try_continue 显式参数 agentic.rs:342 run_agentic_loop(conv_id:String)入参 · :352 guard.new(conv_id.clone()) · :492-495 stop_flag/notify 取 per_conv(conv_id 同源) · :520-521 acquire_per_conv(&conv_id) · commands.rs:385/488 try_continue 传 approval.conversation_id · agentic.rs:1080-1083 try_continue(conv_id:&str)显式入参 · :1099-1100 has_pending 按 conv_id 过滤(决策e 真并发准备) · :1216 续跑传 conv_id_owned
F-stop_flag/notify互通 loop 取 per_conv Arc.clone + IPC 写 per_conv(双写互通) agentic.rs:492-495 (conv.stop_flag.clone(), conv.notify.clone()) 取 per_conv Arc · commands.rs:1023/1035/1026/1037 ai_chat_stop 双写 per_conv.stop_flag + 顶层 stop_flag(行为互通非依赖共享 Arc) · :1042-1046 notify 走 per_conv.notify.clone()(同源 loop 监听的 per_conv Arc)→ stream_llm select! 监听同源 notify_one 互通 (见 low)
G-conv/conv_read去allow 批2 迁移后访问器去 allow mod.rs:427 conv / :437 conv_read无 #[allow(dead_code)](已消费);仅 :541 PerConvState struct 仍标 allow(SW-260618-21 b 预留/批2 字段全活跃后 allow 注释待清,非阻塞)
H-audit迁移 process_tool_calls per_conv 读写 audit.rs:464 conv_read 取 messages 缓存 · :640 conv_read 读 session_trust · :678/708/769/823 conv() 写 tool_result messages · conv_id 来源函数入参(本 conv 工具调用)
I-conversation/title/knowledge/lib迁移 各模块 per_conv 读写 + fallback conversation.rs:154 conv_read fallback 顶层 messages · title.rs:50 conv_read fallback(switch 触发 spawn 可能无 per_conv) · knowledge_inject.rs:257 conv_read fallback 顶层 messages.len() · lib.rs:43-54 L0 兜底读 per_conv.generating fallback 顶层 + 双写复位(启动残留 generating 清理)
J-编译/测试 cargo check 0 warning cargo check --workspace EXIT 0 0 warning(Finished in 0.64s,无 dead_code 新增——批2 字段全活跃)

对抗核验印证:

  • 三处退出校验语义一致? 三处 !per_conv.contains_key(&conv_id) + warn + return,防 push 写回已删 conv 污染他人(决策 e 真并发准备)。conv_id 来源同源(run_agentic_loop 入参),无串台风险。
  • 双写覆盖所有写路径? grep commands.rs 全部 session.conv( 写路径后均有对应顶层 session.* 双写(generating/stop_flag/messages/iteration_used/model_override/agent_language),批4 IPC 迁移后顶层移除(注释明确标注批4)。
  • stop_flag/notify 是否真共享同一 Arc? ⚠️ 注释不准确但行为正确:PerConvState::new(mod.rs:576-588)创建全新 Arc::new(AtomicBool)/Arc::new(Notify),commands.rs ai_chat_send 入口 session.conv(&active) 惰性创建新默认 PerConvState(未搬入顶层 Arc)。注释 agentic.rs:490/commands.rs:1041 声称「入口桥接时顶层 Arc 直接搬入」「共享同一 Arc」与实现不符。但行为正确:ai_chat_stop 双写 per_conv.stop_flag + 顶层 stop_flag(:1035/1037),loop 读 per_conv.stop_flag 被命中;notify 走 per_conv.notify.clone()(:1042)与 loop 监听同源。双写兜底保证互通,非依赖共享 Arc。详见 low-1。
  • try_continue conv_id 解耦正确? R-PD-6:审批等待态 active_conversation_id 可被切走,try_continue 用 approval.conversation_id 显式入参(:1083)+ has_pending 按 conv_id 过滤(:1099),不读全局单例。续跑 run_agentic_loop 传 conv_id_owned(:1216)。
  • 单会话全功能回归? send(ai_chat_send 双写占用+spawn)/stop(ai_chat_stop 双写 stop_flag+notify)/approve(commands.rs:385 try_continue 传 approval.conversation_id)/switch(active_conversation_id 切换 + 旧 loop conv 存在性退出)/regenerate/edit/force_send 全路径双写,行为等价批1 前单 active 场景。

low(可选·非必修):

  1. stop_flag/notify「共享同一 Arc」注释误导(agentic.rs:490/commands.rs:1041/mod.rs 相关注释):声称 per_conv.stop_flag/notify 与顶层共享同一 Arc(入口桥接搬入),实际 PerConvState::new 新建 Arc,commands.rs 入口未搬入。行为正确(双写 + 同源 per_conv Arc clone 互通),仅注释与实现不符。建议注释改为「双写兜底互通(loop 取 per_conv Arc.clone,IPC 写 per_conv 同源),非共享同一 Arc 实例」,或批4 IPC 迁移后顶层 stop_flag/notify 删除时一并清注释。
  • 关联:F-09 阶段2 批2。
  • 待修项回流 todo: 🔴/🟡 项(1 low 注释修正可选,非阻塞,批4 清顶层字段时一并处理)

CR-260619-05 F-09 batch5 llm_concurrency会话级(决策c)(agent f09-batch5·主代兜底·commit d960417) — 已审(PASS·1 WATCH 修正·独立 grep/read 核验)

  • 范围(7 文件):state.rs(per_conv HashMap<conv_id,Semaphore> + per_conv_permits AtomicUsize 热改 + acquire_per_conv(conv_id) + release_conv + set_per_conv 软收敛)+ agentic.rs(loop 入口 acquire_global+acquire_per_conv(conv_id) 整 loop 持有,会话级并发上限 3)+ compress/title/knowledge_inject(签名 conv_id + acquire_per_conv)+ project.rs(扫描合成 key)+ commands.rs(ai_conversation_delete release_conv)。
  • 核心:global LLM 调用并发→并发会话数上限(每 loop 1 permit 整 loop);per_conv 单信号量→HashMap(每对话 permits=2);F-260616-12 retry permit 自洽。

复审结论(2026-06-19·独立 grep/read 核验源码当前形态·稳定态 commit d960417·cargo check 0 warning): PASS🔴0 🟡0 1

逐项核验表(file:line 佐证 + 判定):

核验点 佐证 判定
A-global语义 会话级·每 loop 入口拿 1 permit 持整 loop state.rs:116-117 global:Arc<Mutex<Arc>> · :116 注释「permits=默认 3,run_agentic_loop 入口拿 1 持整 loop」· :143-146 acquire_global lock+clone Arc+acquire_owned · agentic.rs:520 let _conv_global_permit = llm_concurrency.acquire_global().await 在 for iteration 循环外(loop 入口),整 loop 持有含工具执行/审批等待/重试,函数返回 Drop 释放
B-per_conv HashMap 取/建/清理 + permits 热改 state.rs:120 per_conv:Arc<Mutex<HashMap<String,Arc>>> · :125 per_conv_permits:Arc(热改) · :150-158 acquire_per_conv lock HashMap → or_insert_with(permits 当前值)→ clone Arc → 释放 lock → acquire_owned · :165-167 release_conv lock+remove · :178-182 set_per_conv store AtomicUsize + clear HashMap(软收敛)
C-release_conv时机 仅 ai_conversation_delete 调用(conv 删即清) commands.rs:1737 state.llm_concurrency.release_conv(&conversation_id).await 在 ai_conversation_delete 内 · :1726 per_conv.remove 同步 · :1736 drop(session) 再取 LlmConcurrency 锁(避免锁序) · 注释 :1731-1735 说明 loop 正常收敛/达 MAX/stop 但 conv 未删不清理(复用限流计数)
D-F-260616-12 retry retry 同 loop 内持 per_conv 合理 agentic.rs:521 acquire_per_conv(&conv_id) loop 入口 · :512-519 注释明确「retry 同 loop 内,持 per_conv 合理;global 是会话级,retry 不再阻塞他对话(原每轮 acquire/drop 语义下 global 短暂释放,现整 loop 持有更贴合会话级并发上限语义)」 · retry 在 stream_one_provider 内(:236 重试),同 loop 同 conv,permit 不释放正确
E-两HashMap独立 AiSession.per_conv(状态) vs LlmConcurrency.per_conv(限流) mod.rs:357 AiSession.per_conv:HashMap<String,PerConvState>(会话状态真相源) · state.rs:120 LlmConcurrency.per_conv:Arc<Mutex<HashMap<String,Arc>>>(限流槽) · 两者独立 HashMap 无耦合 · 批2 双写 AiSession(per_conv 状态+顶层桥接)与 batch5 限流无冲突
F-set_per_conv软收敛 热改 permits + 清 HashMap state.rs:174-182 set_per_conv store AtomicUsize + clear HashMap · 注释 :175-177「旧 permit 随 Drop 释放,新对话 acquire 用新 permits 重建 Semaphore;已建对话若仍在跑,旧 Semaphore 不变,下次该 conv 新 acquire 时因 HashMap 已清空会重建为新 permits」 · 软收敛语义正确(旧 permit 不抢占) · commands.rs:1886 ai_set_concurrency_config 调 set_per_conv
G-compress/title/knowledge接入 签名 conv_id + acquire_per_conv compress.rs:85 acquire_per_conv(conv_id) · title.rs:190 acquire_per_conv(conv_id)(注释 :188 per_conv 改 HashMap,标题针对本对话) · knowledge_inject.rs:393 acquire_per_conv(conv_id)(注释 :391 知识提炼针对本对话) · 三处共享同 conv 限流槽(permits=2 容纳主 loop + 1 后台)
H-project合成key 扫描用 "project_scan" 合成 key project.rs:553/649 acquire_per_conv("project_scan") 合成 key · 非真实 conv_id 但统一限流(扫描任务共享一槽,防并发扫描失控)
I-编译/测试 cargo check 0 warning cargo check --workspace EXIT 0 0 warning

对抗核验印证:

  • global 整 loop 持有(含审批等待)? acquire_global 在 for iteration 外(:520 loop 入口),审批等待 return(:991)/收敛/达 MAX/stop 各 return 点函数返回,_conv_global_permit Drop 释放槽位。
  • 审批等待占 global 槽? ⚠️ 修正 prompt WATCH⑥:审批等待路径(:988-991)disarm guard + return,函数返回释放 _conv_global_permit/_conv_per_conv_permit。审批等待期间不占 global 槽,续跑 try_continue → run_agentic_loop(:1216)重新 acquire。prompt WATCH⑥「审批等待占 global 槽阻塞他对话」描述不准确,实际审批等待释放槽位不阻塞他对话——这是合理设计(审批等待是用户交互态,不应占并发槽)。详见 WATCH-1。
  • per_conv permits=2 容量够? 主 loop stream_llm(1)+ 后台标题/压缩/提炼(各 1)同 conv 共享 permits=2。常态主 loop 持 1 + 至多 1 后台任务(标题或压缩或提炼之一)持 1 = 2 满载,第 3 个后台任务 await 排队。单对话内并发上限 2 合理(防失控)。
  • release_conv 仅 delete 无残留? ai_conversation_delete(:1737)conv 删即清 LlmConcurrency.per_conv 条目。已持 permit 不受影响(permit 绑旧 Arc 随 Drop 释放),仅阻止新条目累积防 HashMap 无限增长。loop 正常收敛但 conv 未删时不清理(下次发消息复用,限流计数连续)。
  • set_per_conv 清 HashMap 是否丢正在跑对话的限流? 软收敛:已建对话旧 Semaphore 不变(permit 绑旧 Arc),HashMap 清空仅影响新 acquire(重建为新 permits)。旧 Semaphore 随最后 permit 释放而 drop。无丢限流风险。
  • 与批2 双写兼容? batch5 仅改 LlmConcurrency(state.rs)+ 接入点 acquire/release,不动 AiSession.per_conv 状态结构。两 HashMap 独立,批2 双写 AiSession(per_conv 状态+顶层)与 batch5 限流无耦合。cargo check 0 warning 印证兼容。

WATCH:

  1. 审批等待占 global 槽(修正):prompt WATCH⑥ 声称「审批等待占 global 槽阻塞他对话(决策c有意)」描述不准确。实际审批等待路径(agentic.rs:988-991)disarm guard + return,函数栈帧退出释放 _conv_global_permit,审批等待不占 global 槽。续跑 try_continue→run_agentic_loop 重新 acquire。这是合理设计(审批是用户交互态不占并发槽)。无问题,仅修正 prompt 描述。另:达 MAX 未收敛转暂停态(:1003- 路径)同样 return 释放槽位,续跑 ai_continue_loop→try_continue→run_agentic_loop 重新 acquire,一致。
  • 关联:F-09 阶段2 batch5(决策c-1 落地)。
  • 待修项回流 todo: 🔴/🟡 项(WATCH-1 为 prompt 描述修正,非代码问题)

CR-260619-06 F-09 batch8 启动恢复多conv restore+L0清残留(纯后端)(agent f09-batch8·主代兜底·commit 6ad4ec2) — 已审(PASS·1 WATCH + 🟡1 跨批遗留·独立 grep/read + git diff 时序对抗核验·不跑 cargo 避 batch5 中间态)

  • 范围(2 文件):audit.rs restore_pending_approvals 多 conv 分配(按 conversation_id 分组惰性建/复用 PerConvState,无主 None 不建 R-9)+ 2 单测(分配不变量/全 None 无 per_conv);lib.rs L0 握手遍历 per_conv 清多 conv 残留 generating(dirty_convs + 逐个复位 + 各发 AiCompleted 补偿 + active 兜底)。
  • 审查要点:① restore 多 conv 分配正确性(convs_restored 去重 + conv() 幂等惰性建);② 无主 None 不建 per_conv(R-9);③ L0 遍历全 per_conv(非单 active)清残留;④ 各 dirty conv AiCompleted 补偿(按 conversation_id 路由);⑤ active 兜底(双写期边界);⑥ 与双写兼容(顶层双写复位);⑦ 不改 IPC 签名/前端/顶层字段。
  • 验证(主代兜底):cargo check --workspace 0 + test 98(96+2 新)+ grep restore 多 conv/L0 遍历印证。
  • 关联:F-09 阶段2 batch8(F-09 后端完整;batch4 启用·前后端待用户)。

复审结论(2026-06-19·独立 grep/read 核验源码当前形态 + git diff 跨版本时序对抗·commit 6ad4ec2·不跑 cargo 避 batch5 llm_concurrency 中间态误报·memory review-batching-worktree-transient): PASS🔴0 🟡1(跨批遗留·非 batch8 引入)1

逐项核验表(file:line 佐证 + 判定):

核验点 佐证 判定
A-restore 多 conv 分配 convs_restored HashSet 去重 + conv() 幂等惰性建 audit.rs:306 let mut convs_restored: HashSet<String> · :316-323 if let Some(cid)=rec.conversation_id.as_deref() { if !cid.is_empty() && convs_restored.insert(cid.to_string()) { let _ = session.conv(cid); } } — insert 返 true 仅首次触发 conv(),同 conv 二次 insert 返 false 跳过(去重);conv() 内 entry().or_insert_with(PerConvState::new)(mod.rs:427-431)已存在复用同实例,幂等无副作用
B-无主 None 不建 per_conv(R-9) conversation_id=None 不进 if 分支 audit.rs:316 if let Some(cid)=rec.conversation_id.as_deref() — None 不匹配 Some 模式,跳过 conv() 调用,直接走 :324-338 insert pending_approvals 单层表;:309-312 risk 解析失败 continue 守卫保留(损坏记录不恢复,CR-22 销账后语义)
C-pending_approvals 单层 不进 per_conv,conversation_id 保留业务语义 audit.rs:324-338 session.pending_approvals.insert(tool_call_id, PendingApproval{..conversation_id: rec.conversation_id..}) — conversation_id 作业务字段保留非路由键(mod.rs:293-295 注释印证);recovered:true / diff:None(AE-2025-03)
D-DB 字段真实 AiToolExecutionRecord.conversation_id 存在 df-storage/models.rs:206 pub conversation_id: Option<String> · conversation_repo.rs:242 list_pending()Vec<AiToolExecutionRecord> · :95 from_row conversation_id: row.get("conversation_id")? 真实读 DB 列
E-L0 遍历全 per_conv 非 active 单值,遍历 HashMap 全 dirty conv lib.rs:47-52 `let mut dirty_convs: Vec = session.per_conv.iter().filter( (_,c)
F-逐 conv 复位 + 顶层双写 每 dirty conv generating=false + 顶层同步 lib.rs:55-59 for cid in &dirty_convs { if let Some(conv)=session.per_conv.get_mut(cid) { conv.generating=false; } } · :61 session.generating=false(批2 双写桥接复位)
G-active 兜底 双写期顶层=true 但 per_conv 空边界 lib.rs:66-70 if let Some(active)=session.active_conversation_id.clone() { if !active.is_empty() && !dirty_convs.contains(&active) { dirty_convs.push(active); } } — 保证 active conv 至少收一个 AiCompleted(active_conversation_id 字段 mod.rs:288 Option 真实存在);:67 !dirty_convs.contains(&active) 守卫防重复 emit
H-AiCompleted 补偿路由 各 dirty conv 各发一个,按 conversation_id 路由 lib.rs:72-87 if was_generating { for cid in &dirty_convs { let _ = app_h.emit("ai-chat-event", AiChatEvent::AiCompleted{..conversation_id:Some(cid.clone())}); } } — AiCompleted 变体含 conversation_id 字段(mod.rs:118-125),各 cid 各 emit;:53 was_generating = !dirty_convs.is_empty() || session.generating 守卫无残留不空发
I-2 单测有效性 分配不变量 + 全 None 边界 audit.rs:864-940 test_restore_multi_conv_distribution_invariant:4 pending 行(2 conv-a + 1 conv-b + 1 None)复现分配逻辑 → 断言 per_conv.len()=2 / conv_read 可达 / 同 conv 复用同指针(幂等 as *const _ 相等) / pending_approvals.len()=4(含无主) · audit.rs:943-958 test_restore_only_ownerless_no_per_conv:全 None → 断言 per_conv.is_empty() + convs_restored.is_empty()。两测逐字复现实现分配逻辑,契约对齐 conv()/conv_read()
J-不改 IPC/前端/顶层字段 纯后端,无 IPC 签名变更 git show 6ad4ec2 --stat 仅 audit.rs(+131)/lib.rs(+75/-29 改 setup listener);无 commands.rs IPC handler 变更 / 无前端 ts 改动 / 无 mod.rs PerConvState 字段增删(复用批1 struct)

对抗核验印证:

  • convs_restored 去重 vs conv() 幂等双重保险? 双重保险但非冗余:convs_restored.insert 首次返 true 触发 conv() 一次;conv() 内 entry().or_insert_with 本身幂等(已存在不重建)。即便去重失效(理论),conv() 仍幂等。双重防御,正确性无依赖单一机制。
  • L0 遍历是否真覆盖多 conv? session.per_conv.iter() 遍历 HashMap 全部条目,filter generating 收集所有 dirty conv,非单 active 读。注释 :43-46 显式声明「批2 前仅读 active per_conv.generating 单值;批8 改遍历全部 per_conv 各归各复位」。
  • 批5 llm_concurrency 中间态冲突? batch5 改 LlmConcurrency(state.rs per_conv Semaphore HashMap),与 batch8 改 AiSession.per_conv(状态) + lib.rs L0 listener 完全独立。两 HashMap 独立无耦合(CR-260619-05 E 项已印证)。本次不跑 cargo 正是避 batch5 调整中间态致误报。
  • restore 在 session.lock 内调 conv() 借用安全? audit.rs:302 let mut session = state.ai_session.lock().await 取独占锁,:321 session.conv(cid) 取 &mut PerConvState 在锁内单次借用,:324 session.pending_approvals.insert 另一字段借用——两次顺序借用(非同时),Rust 借用检查通过(主代 cargo check --workspace EXIT 0 印证,非本审查跑)。
  • filter !cid.is_empty() 必要性? 防空串 conv_id 惰性建无意义 per_conv(空串非合法 conv_id);conv() 本身不校验空串,restore 侧显式守卫合理防御。

🔴🟡 时序对抗降级(L0 clear 致冷启动审批丢失)— 非批8 引入,跨批遗留:

冷启动时序核验确认「L0 clear 抹掉 restore 重建的 pending」潜在路径存在,但归属判定修正:

环节 核验 佐证
1. restore 填充 真实 state.rs:340 restore_pending_approvals(&state).await 在 init 内 · lib.rs:25 block_on(AppState::init) 同步执行 · audit.rs:291 从 DB list_pending 重建到 session.pending_approvals 内存
2. L0 clear 触发 真实 useAiEvents.ts:386 emit('ai-client-ready') 在 startListener 内(AiChat onMounted 必挂载触发)→ lib.rs:38 listenerlib.rs:62 session.pending_approvals.clear() 无条件清空
3. 前端不显示 真实 ai_pending_tool_calls(commands.rs:507-517)数据源 = session.pending_approvals.values() 内存(非 DB),clear 后返空
4. 审批报错 ⚠️ 部分不成立 ai_approve(commands.rs:328 内存 remove None)→ :334 find_by_tool_call_id 查 DB status=pending → :336 白名单「executed/rejected/failed」不含 pending → :346 return Err「未找到挂起的审批」前端步骤 3 已拉不到 pending,用户根本看不到审批卡不会触发 ai_approve,步骤 4 是理论路径非实际触发

🔴🟡 归属修正(推翻「batch8 引入回归」判定):

git show 5c15b72:src-tauri/src/lib.rs(批2,即 batch8 前一版)L0 listener 已有无条件 clear pending:

  • if was_generating { ...; session.pending_approvals.clear(); ... }(was_generating=true 分支)
  • else { session.pending_approvals.clear(); }(was_generating=false 分支)
  • 两分支都 clear = 无条件 clear(行为等价 batch8 :62 外层单处 clear)

git show 6ad4ec2 -- src-tauri/src/lib.rs diff 印证:batch8 仅重构 clear 位置(批2 两分支各一处 → batch8 外层一处合并),session.pending_approvals.clear() 出现次数 2→1(净减,非新增)。batch8 未引入 clear,更未引入回归——潜在冷启动审批丢失问题批2 起就存在,属跨批遗留。

git log -S "session.pending_approvals.clear()" -- src-tauri/src/lib.rs 印证:该字符串首次引入在 d2cb38c(任务推进链,B-260616-01 L0 握手首引),batch8 仅改变出现次数(合并)。clear 非 batch8 新增铁证

设计契约矛盾确认(跨批遗留,非 batch8):commands.rs:1672-1675 switchConversation retain(|_, a| a.conversation_id.as_deref() != Some(&conversation_id)) 注释明说「防 init 重建的内存 HashMap 被清空,重启恢复链路:restore → switchConversation(此处不清目标)→ ai_pending_tool_calls → ai_approve」——该恢复链前提是 restore 重建的 pending 在 switchConversation 时仍在内存,但 L0 clear(批2 起)在冷启动 AiChat onMounted 时就触发(早于用户手动 switchConversation)抹掉 restore 成果。矛盾真实存在,但归属批2+,非 batch8

为何另一位审查 agent 误判 batch8 引入:git show 6ad4ec2 -- lib.rs 只看 batch8 diff 内的 + session.pending_approvals.clear()(那是合并后上提的外层一处),未对比批2 版本(5c15b72)已有两处 clear,误判为新增。印证 memory code-review-anti-contamination:对抗核验须跨版本 git diff,不能只看单 commit diff 的 +/- 行。

WATCH-1(AiCompleted 补偿前端「各归各复位」描述精度,low):lib.rs:74 注释 + prompt 声称「前端 useAiEvents.ts:133-140 按 conversation_id 路由各归各复位」,实际 useAiEvents.ts:133-140 isCurrent=false(非当前展示 conv)收到 AiCompleted 时清的是全局单值 state.generatingConvId=null(:136),非按 conv 各清。因 devflow 前端 generatingConvId 是单值(同时刻只展示一对话),多 conv 后台跑 loop 切走后完成时清全局 null 无副作用。补偿事件设计意图(防 HMR 残留 generating 卡死)达成,描述略宽泛。建议注释改为「前端按 conversation_id 过滤,非当前 conv 收到 AiCompleted 清全局 generatingConvId(单值)」。非阻塞。

🟡 WATCH-2(L0 clear 致冷启动审批丢失·跨批遗留·非 batch8):L0 lib.rs:62 session.pending_approvals.clear() 无条件清空,抹掉 state.rs:340 restore_pending_approvals 重建的 pending → 冷启动后 ai_pending_tool_calls(内存数据源)返空 → 待审批 toolCard 不显示。归属批2+(5c15b72 起两分支无条件 clear),非 batch8 引入。batch8 仅合并 clear 位置(行为等价)。修复方向(回填 todo,供主代/用户评估,不属 batch8 审查范围必修):

  • 方案 A(推荐):lib.rs:62 clear()retain(|_, a| !a.recovered)。仅清非 recovered(本次会话产生的死 pending / HMR 残留),保留 restore 重建(PendingApproval.recovered=true,audit.rs:331)。对齐 switchConversation 保护意图,HMR 清死 pending 与冷启动保 restore 两全。
  • 方案 B:删 lib.rs:62。pending 清理由 switchConversation retain / delete_conversation retain / ai_chat_clear 各路径精确管理。
  • 判定:此 🟡跨批遗留问题曝光(批2 起就存在),batch8 未恶化(行为等价)。是否修属产品决策(冷启动审批恢复是否是承诺功能),非 batch8 审查必修项。

⚠️ batch5 中间态说明(审查范围外记录):

  • 本次审查全程未跑 cargo(memory review-batching-worktree-transient:F-09 batch5 agent 正在改 agentic.rs/state.rs,跑 cargo check 抓中间态致误报)。源码形态核验 + git diff 跨版本时序对抗 > check 快照。主代兜底 cargo check --workspace EXIT 0 + test 98 passed 为稳定态报告(本审查采信但不复跑)。

  • 待修项回流 todo: 🔴 项(batch8 本身 PASS 无回归) · 🟡 1 项跨批遗留(BUG-260619-06-cross-batch L0 clear 致冷启动审批丢失·归属批2+ 非 batch8·方案 A retain(!recovered) 推荐·供主代/用户评估是否修,非 batch8 审查必修)

CR-260619-07 F-09 batch4 启用多会话上线(决策e真并发·前后端·agent f09-batch4·commit d899c58) — 已审(PASS·2·巡检 2026-06-19 独立 grep/read 核验)

  • 范围(前后端大改,F-09 上线):IPC 签名加 conv_id(ai_is_generating/ai_chat_send/ai_chat_force_send/ai_chat_stop + 内部移除 active 一致性校验)+ 删 switch readonly(决策 e)+ 删 ai_conversation_create 强制结束旧 loop + 删双写桥接/顶层字段(messages/generating/stop_flag/notify/iteration_used/agent_language/model_override/session_trust 全删,per_conv 唯一真相源)+ 删双写代码(全模块)+ pending_approvals retain 目标 conv + finalize_pending_placeholders 按 conv_id + 前端(api/ai.ts+useAiSend/useAiWindow)传 conv_id。保留 session_state(SW 预留标 allow)+ readonly(前端不读)。
  • 审查要点:① IPC 签名 conv_id 前端传参(api/ai.ts/useAiSend/useAiWindow);② 删 readonly 后 switch 生成中可切(决策 e)+ 后台 conv 跳过 reload;③ 顶层字段全删(per_conv 唽一)+ 双写清理完整(grep 顶层引用 0);④ conv_id 上下文一致(前端 activeConversationId);⑤ pending_approvals retain 目标 conv(不误杀他 conv);⑥ switch reload 边界;⑦ 保留项(session_state/readonly)合理(零调用方预留按用户指导保留)。
  • 验证(主代兜底):cargo check --workspace 0 + vue-tsc 0 + test 98 + grep 顶层删除/per_conv 唯一印证。
  • ⚠️ 双会话回归留用户验收:开 A 跑→切 B 发→A 后台不退出不污染 B;审批/max 续跑跨会话。后端 per_conv 隔离 + 事件 conversation_id 路由保障。
  • 关联:F-09 完整(batch1-8+batch4 上线)。

复审结论(2026-06-19·巡检独立 grep/read 核验源码当前形态 + 决策e 真并发对抗分析·commit d899c58): PASS🔴0 🟡0 2

7 审查要点逐项核验(file:line 佐证):

# 要点 佐证 判定
IPC conv_id 前端传参 api/ai.ts:sendMessage/forceSend/regenerate/edit/continue/stop/ai_is_generating 全加 conversationId;useAiSend.ts:doSend :125/127 传 state.activeConversationId、regenerate :174-183/edit :244-252 有 if(!convId) null 保护
删 readonly 后台 conv 可切 agentic.rs:546 loop 退出判据 !per_conv.contains_key(&conv_id)(conv 存在性,非 active 一致性);:539-541 注释「旧 loop 跑自己 conv 不污染他人,active 切换不让退出」
顶层字段全删 + 双写清理 grep session.generating/messages/stop_flag/notify/iteration_used/agent_language/model_override/session_trust 全命中注释(agentic.rs:400/993、mod.rs:474 等),无实际顶层读写代码(cargo check 0 印证)
conv_id 上下文一致 run_agentic_loop:338 conv_id 入参快照,全程 session.conv(&conv_id);try_continue_agent_loop:1075 显式 conv_id 入参(:1214 注释「active_conversation_id 字段移除,改入参 conv_id」);loop 内 grep active_conversation_id 无实际读写
pending_approvals retain 目标 conv switchConversation(mod.rs:482)/deleteConversation(:526)/ai_chat_clear(chat.rs:536)/ai_chat_stop(:895/1015)全 retain(... != target),不误杀他 conv,保护 restore 重建链路(注释 :480-481)
conv_id=null fallback ai_is_generating:178-182(null→fallback active→空返 false)/ai_chat_send:218-233(null→active→懒创建 new_id)/ai_chat_stop:994-998(空返 Ok)
ai_continue/stop_loop 移除 active 校验 chat.rs:1094/1129 实际只 conv_read(&conv_id).generating 校验,无 active 一致性(决策e 落地)

对抗核验印证(防污染):

  • CR-06 修复延续 (关键):lib.rs:62 session.pending_approvals.retain(|_,a| !a.recovered) 在 batch4 后仍保留(:59-62 注释「BUG-260619-06 修复」)。batch4 删顶层 generating 双写复位(:45)+ 删 active 兜底(顶层退役后无意义),但 retain 修复完整保留 → 上轮审 CR-06 的 🔴 未被新批破坏。
  • 决策e 真并发核心 :loop 退出判据 conv 存在性(:546)+ loop conv_id 快照不读 active(:348/368)+ guard/stop_flag/notify/messages 全 per_conv(:97/1100/1135)+ 事件 emit 全带 conversation_id → A 后台 loop 切 B 不退出不污染,设计契约与 switchConversation retain 一致。
  • 主代自审 PASS + cargo check 0 + test 98,独立核验源码形态印证(非信声明)。决策e 真并发时序保障经多维度交叉(前端传参/后端 fallback/loop 快照/退出判据/retain 路由)成立。

low(2):

  1. 注释过时(6 处,功能正确但误导维护):agentic.rs:400(model_override「入参←session」→ 实为函数入参)/:993(session.iteration_used→ 实为 per_conv)/chat.rs:1079(session.iteration_used→per_conv)/:1082-1083&:1117-1118(ai_continue/stop_loop 文档注释「校验 active 一致性」→ batch4 已移除)/mod.rs:474(批2 迁移说明残留)。建议批量更新对齐 batch4 per_conv 化 + 移除 active 校验。
  2. 双会话实机验收(CR-07 :883 已声明):开 A 跑→切 B 发→A 后台不退出不污染 B;审批/max 续跑跨会话。后端 per_conv 隔离 + 事件 conversation_id 路由 + loop 退出判据 conv 存在性已核验保障,真并发时序(两 conv 同时 loop + provider pool/llm_concurrency 竞争)需实机验收。
  • 待修项回流 todo: 🔴/🟡

CR-260619-08 df-ai 会话意图识别层 intent.rs 新建(纯函数模块·不接入 loop·commit 7724cb7) — 已审(PASS·1 low·独立 grep/read 核验)

  • 结论(2026-06-19·独立 grep/read 核验 commit 7724cb7): PASS🔴0 🟡0 1
  • 范围:crates/df-ai/src/intent.rs(新建 682 行)+ crates/df-ai/src/lib.rs(pub mod intent; 注册 4 行)。

6 维度逐项核验(file:line 佐证 + 判定):

维度 核验点 佐证 判定
① 识别规则 优先级取舍 + 置信度封顶 + 求和 recognize:289-301 依次 best_in_group(SPECIFIC→ENTITY→GENERIC) 命中即 return 不向下累积 · best_in_group:305-329 组内 score=Σ命中权重 :315 + .min(1.0):322 封顶 · 平局 b>=conf 保留旧:324(SPECIFIC_GROUP 数组 Code 首位 :200 同分 Code 胜)
② 工具名对齐 ToolDomain 29 工具名逐条 vs tool_registry.rs 自动化 perl 比对:registry 29 == intent 29,in registry but NOT in intent: (none) / in intent but NOT in registry: (none) · Data 18(list_projects..get_task_count)+File 10(read_file..rename_file)+Http 1(http_request)与 tool_registry.rs:1838-1852 基线测试 expected 完全一致
③ ModelTier None 预留 恒 None,未误接 provider/model suggested_model_tier:376-379 body 仅 None + TODO 注释 · ModelTier 枚举 :80-87 仅 Fast/Standard/Heavy 定义,无 provider/model 关联 · 单测 :653-671 遍历 11 意图全断言 None
④ 独立性 不接入 loop/不读 registry/不碰 src-tauri git show --stat 7724cb7 仅 2 文件(intent.rs 新增 + lib.rs +4)·lib.rs:12 仅 pub mod intent; · intent.rs grep 无 use crate::/commands/tool_registry/agentic 运行期依赖(仅文档注释 :6/:15-16/:222-223 提及)·无 src-tauri 改动
⑤ 测试覆盖 36 单测覆盖关键路径 cargo test -p df-ai --lib intent:: 36 passed 0 failed(EXIT 0 复跑印证无中间态漂移)·覆盖:枚举 as_str / recognize 中英文 / 优先级 SPECIFIC>ENTITY>GENERIC / 边界空串+空白+无关键词 / 置信度封顶 / tool_subset 各 domain + fallback 空 / 跨 domain 去重 / ModelTier None / Default 构造
⑥ 设计文档对齐 方式 A 规则识别 意图识别层论证-2026-06-19.md 第 11 条「方式 A 规则(零延迟零成本)落 intent.rs + tool domain 标签」+ 第 58 条「子集扩充非裁剪 / None fallback 全量零回归」+ 第 13 条「loop 入口生效一次不进 loop 体」全对齐实现 · 文档预估 ~200 行,实现纯逻辑约 380 行(682 含 36 单测 ~300 行)量级合理

对抗核验印证:

  • 维度① 平局处理:recognize_debug_zh 测试输入「bug 复现 调试 排查」(bug→Code=1.0 / 复现0.9+调试1.0+排查0.9=2.8→1.0→Debug)同分 1.0,Code 在 SPECIFIC_GROUP 首位先遍历,b>=conf(1.0>=1.0)保留 Code → 返 Code 测试 :420 断言 Code 正确反映此行为
  • 维度② 维度声明文档笔误:登记项写「Data 18/File 10/Http 1」(暗示 28)但实际 18+10+1=29。tool_registry.rs:1830 基线测试断言 29(18 data + 10 file + 1 http)。intent.rs 正确对齐 29,非 28。声明笔误不影响代码正确性,仅文档表述
  • 关键词歧义点(规则识别固有局限,非 bug):"修改"(File 0.7)+ "项目"(Project 1.0)同命中 ENTITY 组,File 在 ENTITY_GROUP 首位先遍历 → "修改项目名称"会识别为 File 非 Project。方式 A 准确率 70%+(设计文档第 27 条声明),子集扩充非裁剪 + None fallback 零回归兜底,非 high/med
  • 测试用例歧义容忍:recognize_search_zh :454-461 输入「搜索 代码 grep」(Search 搜索1.0+grep1.0=2.0→1.0 vs Code 代码1.0→1.0 同分,Code 首位胜 → 返 Code),用 matches!(Search|Code):460 容忍已知歧义 测试合理(见 low-1)

low(可选·非必修):

  1. recognize_search_zh 测试注释与用例自相矛盾(intent.rs:457-459):注释说「调整用例避免歧义」但用例本身仍触发歧义(仅靠 matches!(Search|Code) 容忍)。建议要么改输入为纯 Search 关键词(如 "grep 查找" 已有 recognize_search_pure :464 覆盖,本用例冗余),要么删注释「调整用例」表述保留 matches! 容忍说明。纯测试可读性,非功能。
  • 待修项回流 todo: 🔴/🟡

CR-260619-09 F-260619-03 文件访问权限模型 Phase B+C + anthropic_compat 连续user合并(后端 state/audit/chat/conversation/mod/lib + 前端 DirAuthDialog 等 15 文件·commit bddbfd4 + 7c98134) — 已审(PASS·🟡12)

  • 结论(2026-06-19·独立 grep/read 核验 commit bddbfd4+7c98134): PASS🔴0 🟡1 2
  • 验证: cargo test -p devflow --lib state:: 15 passed 0 failed(Phase A/B/C 全覆盖) / cargo check -p devflow EXIT 0 无 warning / vue-tsc --noEmit EXIT 0。独立 grep 核验 6 维度源码当前形态,不信 agent 自报。

6 维度逐项核验:

# 维度 判定
1 权限模型正确性 session 进程级全局(Arc<RwLock<AllowedDirs>> state.rs:305),clear 时机完整(conversation.rs create:74/switch:151/delete:230);NeedsAuth 挂起恢复链路完整(pending_count+=1 audit/mod.rs:283 + 占位 tool_result :299 + emit AiDirAuthRequired :300 + ai_authorize_dir→try_continue chat.rs:628)
2 黑名单完整性 Win(System32/SysWOW64/System/Program Files×2) + Unix(/etc /usr /bin /sbin /boot /dev /proc /sys)覆盖合理;分段精确匹配防误伤(test_blacklist_no_false_positive 印证 "my program files backup" 不拒);黑名单优先于白名单双判(is_authorized state.rs:353 + check_path_authorization :441 独立判);write_file 新建路径三层防护:预校验 check_file_tool_auth(audit/mod.rs:253) + handler 词法层 is_authorized(tool_registry.rs:326) + handler canonicalize 层 is_authorized(:334),不存在路径也判黑名单,不绕过
3 写约束覆盖 tool_registry.rs 逐条核验:delete_file=High(:1438) / write_file=Medium(:1061) / patch_file=Medium(:1175) / append_file=Medium(:1401) / rename_file=Medium(:1507);读类 read_file/list_directory/file_info/search_files=Low。路径授权放行后仍走 RiskLevel 审批(正交性:check_file_tool_auth 在 risk_level 分类调 audit/mod.rs:233-259,Authorized drafts 才进下方 Low/Med/High 循环 :325)
4 挂起恢复链路 ai_authorize_dir(chat.rs:520)三分支完整:deny→Err+恢复 loop(:542) / once→add_session_allowed_dir(:575) / always→add_persistent_allowed_dir(:580,失败降级 session);复用 ai_approve 执行链(run_workflow 特殊处理 :589);path_auth 守卫双向严密:ai_approve :362 拦 path_auth 挂起回滚 pending+Err,ai_authorize_dir :534 拦普通审批 ok_or_else Err
5 前端弹窗交互 DirAuthDialog.vue 三选项(once/always/deny)+ 双重守卫(pendingDirAuth + isViewGenerating :55)+ dirAuthActing 防重入(:54);useAiEvents.ts pendingDirAuth 置位(:231)+ 三处清空(AiApprovalResult:335/AiCompleted:357/AiError:407);NO_RESET_WATCHDOG 含 AiDirAuthRequired(:47)不触发整流超时
6 跨会话隔离 clear_session_allowed_dirs(state.rs:673)仅清 allowed_dirs.session,不动 per_conv/pending_approvals;conversation.rs create/switch/delete 的 retain 仅清目标 conv 的 pending;session 全局单例不构成 F-09 回归(Phase B 沿用 active 单全局模型,agent 风险点5 已承认,真多会话独立临时授权需迁 PerConvState 属后续工作)

附带 7c98134 merge_consecutive_users 核验: anthropic_compat.rs:265 合并相邻 user 块为一条 user 含 [tool_result..., text] blocks 数组,String/Array content 双形态规范化(:282-294),while 循环不增 i 续合并多连续 user(:297),逻辑正确,打破 GLM 1214 连续 user 恶性循环。

🟡 MED-1: state.rs:673 + audit/mod.rs:245 + tool_registry.rs:1065 — 预校验(process_tool_calls)与 handler 闭包两端独立 read lock 取 allowed_dirs.session 快照,并发 clear_session(create/switch/delete)下存在"预校验放行→handler 拒绝"窄窗口不一致。mod.rs:251 注释声称"两端授权判定一致"未标注并发限制。后果仅工具返 Err(LLM 收错误自行调整,非数据破坏),触发条件极窄(用户在工具执行瞬间切会话+该会话有 session 临时授权目录)。建议:注释补充"并发 clear_session 下两端快照可能不一致,后果为工具 Err 非数据破坏"说明,或后续 session 字段迁 PerConvState 时顺带消除。

LOW-1: state.rs:412 is_in_system_blacklist Unix 分支未覆盖 /var(日志/spool/cron)和 /root(root 家目录)。影响有限:黑名单是用户误授权兜底,/var/root 非系统核心不可替换目录,且需用户手动授权才触达。后续可按需补充。

LOW-2: state.rs:327 AllowedDirs.session 进程级全局单例,F-09 多会话并发下各会话无法独立临时授权(切走即清,切回需重新授权)。agent 实施报告风险点5 已明确承认此限制,Phase B 对齐 active 单全局模型,非新回归。真多会话独立临时授权需迁 PerConvState.allowed_dirs,属后续工作。

  • 待修项回流 todo: 🔴/🟡 项(MED-1 为观察级注释补充,非阻塞性代码修复;LOW 两项为已知限制/可选扩展)

CR-260619-10 消息级溯源 P0 地基(ChatMessage.id + ai_messages 拆表 + V21 迁移 + AiMessageRepo + audit message_id 列·commit e981c14) — 已审(PASS)

  • 结论(2026-06-19·独立 grep/read 核验 commit e981c14 + cargo test 复跑): PASS🔴0 🟡0 1
  • 验证: cargo test -p df-storage 45 单测 + 11 集成全 passed(V21 5 测:新库空跑/老库迁移/坏数据/幂等重跑/列补建 + message_repo 5 测全绿,无漂移)。git show e981c14 10 文件 diff 逐文件核验。

5 维度逐项核验:

# 维度 判定
1 迁移正确性(migrate_v21 migrations.rs:374) 幂等三重防护(COUNT 探测 :400-407 / INSERT OR IGNORE :472/:489/:505 / schema_version PK IGNORE)+ 坏数据 warn+continue :432-435 + 全量逐条 :438-483 + 新库空跑(V9_SQL :727-744 同步建 ai_messages 表 + message_id 列)+ msg_migrated_{conv_id}_{seq} :442 填 ai_messages.id 正确(V21 测 v21_legacy_db_migrates_messages 覆盖老库迁移 + 字段提取 + seq 递增)
2 id 格式(new_message_id types.rs:134) AtomicU64 COUNTER + ts 并发安全 + 时钟回拨单调(计数器兜底)+ serde #[serde(default)] 老 JSON → None round-trip + 6 构造器(provider.rs:45 system/48 user/51 assistant/54 assistant_with_tools/57 tool_result/63 user_parts)全覆盖 id: Some(new_message_id())
3 AiMessageRepo(message_repo.rs) 5 方法 SQL/参数/返回正确:insert_batch INSERT OR IGNORE 幂等 :80 / list_by_conversation ORDER BY seq :115 / delete_range 左闭右开 + None 末尾 :145-153 / update_status :179 / update_content_by_tool_call_id 定点 :207。baseline_tests(mod.rs:263)断言 ai_messages 白名单 None 合理(专用 Repo 不进通用 query 路径)
4 P0 边界(未误接 P1/P2) AiMessageRepo 仅 df-storage 自身注册 + test,0 外部调用方(grep crates/src-tauri 业务侧 0 命中)/ ContextManager 读写路径未改 / save_conversation 未改双写 / audit message_id None 占位仅 finalize.rs:40 单点(P1 接真值)
5 迁移耦合点(types + migrations) types.rs:75-77 ChatMessage 定义注释「改字段名必须同步更新 migrate_v21」+ migrations.rs:370-373 反向标注「同步标注已在 types.rs 加注释」,双向闭环

主代核查·审查防污染印证:

  • 迁移数据完整性逻辑独立走查:messages JSON → Vec<serde_json::Value> 逐条 :430,seq messages.iter().enumerate() :438(0 起递增),status 归一化 None/空→"active" :458-461(列 NOT NULL DEFAULT 'active' 对齐),created_at timestamp 优先 fallback conv_created_at :466-469。

  • audit 14 处 message_id 说法:范围描述措辞,实际 grep src-tauri 仅 finalize.rs:40 单点构造 AiToolExecutionRecord(AiToolExecutionRecord { 仅 1 处),14 处指未来调用覆盖面。单点 None 占位符合 P0 边界(列已建、值待 P1),非缺陷。

  • workspace 自验 EXIT 0(commit message 自报)+ 本会话 cargo test 45+11 复跑印证无漂移(memory review-batching-worktree-transient P0 与 MCP 并行警惕快照漂移,以实际磁盘内容为准已落实)。

  • WATCH-1: V21_SQL(migrations.rs:498)与 V9_SQL(migrations.rs:727)两处 ai_messages 建表 SQL 镜像,改动须两边同步——已在两处加注释 :496-497 / :724-726 标注,后续加列/改约束需双向维护(低风险,有注释护栏)

  • 待修项回流 todo: 🔴/🟡

CR-260619-11 MCP Server(df-mcp crate + stdio 协议 + CLI 子命令·commit f217e39) — 已审(PASS·2)

  • 结论(2026-06-19·独立 grep/read 核验 commit f217e39 + cargo test df-mcp 8 passed 复跑): PASS🔴0 🟡0 2
  • 验证: cargo test -p df-mcp 8 passed 0 failed 复跑确认(initialize/tools_list 高风险过滤/read_only/路由/High 拒/create+list 往返/visible 谓词全过)。HEAD vs f217e39 在 df-mcp 范围零 diff,源码形态即 commit 形态。19 工具逐条核验 vs tool_registry + df-storage Repo 方法全部存在。

6 维度逐一核验:

# 维度 判定
1 JSON-RPC 协议正确性 protocol.rs:109-127 路由 initialize/initialized/tools/list/tools/call/ping 5 method + 错误码常量 -32700/-32600/-32601/-32602/-32603 齐全。server.rs:60-106 stdin 逐行 read_line + write_response 单行 \n framing 正确。Notification(id=None)不回(:92-102)。initialize 返 serverInfo+capabilities.tools(:117-130)。tools/list 返 {tools:[{name,description,inputSchema}]} 符合 MCP Tool schema(:136-143)。tools/call 返 content:[{type:"text"}] + isError 区分业务错/协议错(protocol.rs:185-201)
2 安全降级三层严密性(本 CR 重点) 严密无漏 — 层①tools/list visible 谓词(server.rs:183-189:risk!=High 非ro / risk==Low ro)过滤 4 个 High(delete_project/delete_task/delete_idea/run_workflow)永不暴露。层②dispatch 兜底(server.rs:163 if spec.risk==RiskLevel::High 拒)+ read-only 下 Medium 也拒(:155 spec.risk!=RiskLevel::Low)。层③handler 二次拒(tools.rs:305/489/590/651 四个 High handler 体内无条件 return error,连 ctx/args 都不读)。对抗核验:purge_project df-mcp 未注册(物理删比软删更危险,数据层 MCP 故意只暴露 delete_project 软删,设计正确非遗漏);方法路由仅 5 个无其他 method 触发工具;文件工具(read/write/patch/list/search/command)0 命中。测试 tools_call_high_risk_is_rejected + tools_call_read_only_rejects_medium + tools_list_excludes_high_risk_by_default 全覆盖
3 不暴露文件系统 grep read_file|write_file|patch_file|list_directory|search_files|run_command|delete_file|rename_file|append_file|http_request 全 df-mcp/src 0 命中。19 工具全为数据层(项目/任务/灵感/工作流/回收站),防绕过应用内路径校验
4 工具复用正确性 19 工具逐条核验:list_projects/get_project/create_project/update_project/delete_project/bind_directory/list_tasks/create_task/update_task/advance_task/delete_task/list_ideas/create_idea/update_idea/delete_idea/evaluate_idea/run_workflow/list_trash/restore_project。复用 df-storage Repo(ProjectRepo/TaskRepo/IdeaRepo)零重复 CRUD。advance_task 走 advance_status_atomic CAS(task_repo.rs:151 WHERE id AND status=expected + deleted_at IS NULL 兜底)。bind_directory 走 find_path_conflict(project_repo.rs:190)+ df-mcp 自实现 normalize_path(tools.rs:695 与 df-project scan.rs:24 逐字一致)。evaluate_idea 本地启发式(tools.rs:623 纯函数,不调 LLM)
5 CLI 子命令 main.rs:16 首参 == "mcp-server" 路由 + run_mcp_server(:28)解析 --db/--read-only/--print-config/-h/--help。default_db_path(:112)Windows %APPDATA%/devflow/devflow[-dev].db(cfg!(debug_assertions)→devflow-dev.db,release→devflow.db)与 GUI 同库。--print-config 输出 {"mcpServers":{"devflow":{"command":"devflow","args":["mcp-server"[,"--read-only"]]}}} (:66-70)。windows_subsystem="windows" 保留(main.rs:2)
6 并发安全 db.rs:22 PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; 确认启用(run_server server.rs:38 复用 Database::open)。仅 stdio/本地无认证,依赖本地隔离(Tauri 桌面应用非网络服务,门槛可接受,与 tool_registry Path Traversal 同评估口径 CR-260618-11 #8)

可选优化 (2):

# file:line 问题 建议
1 tools.rs:664-665 list_trashunwrap_or_default() 吞 DB 错误返空列表。MCP 对外暴露,DB 故障时客户端收 {projects:[],tasks:[],...count:0} 误判「回收站为空」(与 CR-260618-13 SMELL-P0-1 同款降级模式,但 MCP 对外场景误导性更高) match { Ok(v)→v, Err(e)→return err_str(e) } 让客户端感知 DB 故障
2 tools.rs:616 evaluate_idea let _ = repo.update_full(&rec).await; 丢弃写回结果。scores 写回失败时客户端仍收旧 idea + 新 scores(响应用写回前的 idea clone :617)轻微数据不一致 写回失败时 err_str 返回,或响应标注「scores 未持久化」(只读评估场景影响小,可接受现状)

主代核验印证(防污染):

  • agent 自报 8 passed,主代独立 cargo test -p df-mcp 复跑 8 passed 0 failed 一致

  • 登记项维度2 把 purge_project 列为 High 风险工具之一,独立 grep 核验 df-mcp 未注册 purge_project(仅 delete_project 软删),描述偏差非 df-mcp 遗漏 — df-mcp 设计正确(数据层不暴露物理删,tool_registry.rs 有 purge_project 但 MCP 不暴露)

  • 三层安全降级源码佐证齐:visible 谓词 server.rs:183 / dispatch 兜底 server.rs:163 / handler 二次拒 tools.rs:305+489+590+651,四 High 工具三层全覆盖

  • 待修项回流 todo: 🔴/🟡 项(2 可选:list_trash 吞 DB 错误返空列表对外误导 / evaluate_idea 丢弃写回结果,非阻塞)


CR-260619-12 消息级溯源 P1 + 流式失败重试修复(context/audit/知识/knowledge_timeline/chat/useAiEvents·commit d3e7640) — 已审(PASS·🟡11)

  • 结论(2026-06-19·独立 grep/read 核验 commit d3e7640 源码当前形态 + cargo test --workspace 复跑): PASS🔴0 🟡1 1
  • 验证: 独立 grep/read 核验 7 文件源码当前形态(diff 快照对比) / cargo test -p df-ai context::tests::last_ 4 测全绿(last_assistant_message_id_returns_latest / last_user_message_id_returns_latest / last_message_id_none_when_no_such_role / last_message_id_none_for_legacy_no_id) / cargo test --workspace 全 11 crate 0 failed(df-ai 139 passed 含 4 P1 新测 / df-workflow 23 / 其余全 ok)。

5 维度逐项核验(源码当前形态为准,非 diff 快照):

# 维度 判定
1 P1 message_id 传递完整性 audit/mod.rs 6 处 audit_tool_call 全传 current_message_id(:283 rejected / :319 pending / :396 auto_dedup / :429 pending / :483 auto_trust / :536 auto,独立 grep 确认无遗漏·cache.rs:106 仅注释引用非调用) / process_tool_calls :224-227 取 session.conv_read(conv_id).messages.last_assistant_message_id(),agentic/mod.rs:882 assistant_with_tools 已 push 后 :913 才调 process_tool_calls,取到的就是本轮 assistant id / 知识提炼 knowledge_inject.rs:355-361 反向扫描 messages 取末条 assistant id / 4 处 build_knowledge_context(chat.rs:151/317/951/1115)全传 user_message_id.as_deref() / 对抗性核验:新构造 ChatMessage(provider.rs:47/50/53 user/assistant/assistant_with_tools 全 id: Some(new_message_id()))非 None,无 None 泄漏到已处理 assistant
2 老数据兼容 ChatMessage.id=None(老反序列化)→ last_assistant_msg_id/last_user_message_id 返 None(context.rs:483 and_then(|t| t.message.id.clone()) 直接透传 None)→ source_ref 降级 conv:{conv_id}(knowledge_inject.rs:455-458 match / knowledge_timeline.rs:91-94/108-111 match None 分支),无 conv_msg:None 无效值
3 症状1 修复(useAiEvents.ts) AiError :401 flushCurrentText 在 :404 state.currentText='' 前(顺序正确) / flushCurrentText :134 last.role==='assistant' && !last.isError 跳错误气泡(AiStreamRetry :199 push 的 isError 气泡) / AiError 错误气泡 :417 在 flush 之后 push(不冲突) / 对抗性场景核验:AiStreamRetry→AiError 链路下末条是 isError 气泡,flush 跳过不覆盖错误提示 / AiAgentRound/AiCompleted 正常路径末条非 isError,!last.isError 仍 true 正常回填,无副作用
4 症状2 修复(ai_regenerate) chat.rs:109-124 pop_last_assistant_round 返 false 时取 all_messages_clone().last()MessageRole::User,last_is_user=true 不报错直接走重跑路径(末尾 user 等价重发) / false 仅在空对话/末尾非 user 才 conv.generating=false; return Err("没有可重新生成的回复")
5 重试不丢参数 ai_regenerate :172 spawn run_agentic_loop(...) 入口读 session.conv(&conv_id).messages 完整历史(agentic/mod.rs:570/668 全量 messages 迭代),不丢历史参数

🟡 MED-1(src/composables/ai/useAiEvents.ts:401): AiStreamRetry→AiError 失败重试链路下部分回复仍可能丢。场景:流式失败→AiStreamRetry push/更新 isError 气泡(末条变 isError)→重试耗尽→AiError flushCurrentText 取末条(isError 气泡)被 !last.isError 跳过,部分回复不会回填到占位 assistant 气泡(占位气泡 content 仍为空),随后 :404 state.currentText='' 清空,本轮部分回复丢失。纯 AiError(无 AiStreamRetry 前导)路径完全有效,仅重试耗尽链路存在覆盖盲区。建议:flushCurrentText 跳过 isError 时改为回填到「isError 气泡之前的最近非 isError assistant 气泡」,或 AiError 收尾前显式查找占位气泡回填。

LOW-1(src-tauri/src/commands/ai/knowledge_inject.rs:355): 知识提炼 source_ref 取「对话最新 assistant」(反向扫描全量 messages,非提炼窗口 recent 6 条内的最新)。OnComplete 异步 spawn 时序下,若 OnComplete 触发后用户又发新消息(虽 OnComplete 通常即时触发),last_assistant_msg_id 可能指向非真正产出该条知识的 assistant。精度边界非正确性问题,source_ref 仍指向对话内合法 assistant,展示侧兼容。注释已说明设计选择(「确保取到对话最新 assistant,即便它不在提炼窗口内也属于本轮 AI 产出」)。

审查防污染印证:

  • audit 6 处调用方计数:独立 grep audit_tool_call 命中 finalize.rs:22 定义 + mod.rs 6 调用(:283/319/396/429/483/536)+ cache.rs 注释引用。与登记项「6 处」一致,无遗漏无多算。

  • chat.rs build_knowledge_context 调用方计数:独立 grep 命中 4 调用(151/317/951/1115)+ 1 定义(knowledge_inject.rs:200)。与登记项「4 处」一致

  • assistant push 时序:注释声称「agentic.rs:882 已 push」,独立 Read agentic/mod.rs 确认 :882 session.conv(&conv_id).messages.push(msg) 在 :908 has_tool_calls 判断 + :913 process_tool_calls 之前,取到的 last_assistant_message_id 就是刚 push 的本轮 assistant(provider.rs:53 assistant_with_tools 必生成 id 非 None),对抗性核验成立,无 None 泄漏。

  • cargo test --workspace 独立复跑 EXIT 0(非主代自报),11 crate 全 ok,df-ai 139 passed 含 4 P1 新测,印证 memory review-batching-worktree-transient 教训(源码形态 > check 快照)。

  • 待修项回流 todo: 🔴/🟡 项(🟡 MED-1 主代已修:flushCurrentText 改从末尾向前找非 isError 占位 assistant 写入,AiStreamRetry→AiError 链路部分回复不再丢)

CR-260619-13 任务查询按状态过滤(list_tasks status·commit 2f8b13f) — 已审(PASS·1)

  • 结论(2026-06-19·独立 grep/read 核验 commit 2f8b13f): PASS🔴0 🟡0 1
  • 核验:cargo check -p df-mcp -p devflow --manifest-path src-tauri/Cargo.toml EXIT 0。逐维度核验源码当前形态(commit 后):
    • 维度1 status 过滤正确性 :tool_registry.rs:620-622 args.get("status").and_then(|v| v.as_str())tasks.retain(|t| t.status == status)。TaskRecord.status: String(models.rs:59),String == &str 通过 PartialEq 成立;status 不传时跳过 retain(可选)语义正确。df-mcp tools.rs:357-359 list.retain(|t| t.status == *st) 同理。
    • 维度2 7 态完整 :task_advance_node.rs:147 spec enum ["todo","in_progress","in_review","testing","done","blocked","cancelled"] 与 commit 文案 7 态逐字一致。filter 纯 String 相等不校验合法性(未知 status 返空集,无 crash/panic),行为可接受。
    • 维度3 df-mcp 一致 :handler 功能就绪(tools.rs:349,357-359),但 spec(tools.rs:108)只声明 project_id,未声明 status —— MCP 客户端无法从 schema 发现该参数。commit message 已声明「spec 声明待补(中文 Edit 编码限制)」,已知延后。记 WATCH-1。
    • 维度4 不破坏现有 :tool_registry project_id 分支(:614-618)+ offset/limit/has_more(:624-627)路径不变;df-mcp project_id_filter + list_active 不变,新增 status 仅追加 retain。注意 df-mcp 原代码 if let Some(pid) = project_id_filter 改为 &project_id_filter+*pid(借用重构),行为等价(project_id_filter 仅借用未移动)。
  • WATCH-1:df-mcp list_tasks spec(tools.rs:108)未声明 status 参数,MCP 客户端 schema 不可见。commit 已声明延后补(中文 Edit 编码限制),后续补 schema 时一并补 status field。
  • 状态: 已审(PASS)
  • 待修项回流 todo: 🔴/🟡

CR-260619-14 多会话并发前端 UI 阶段3/4(generatingConvs:Set + 侧栏并行 + 独立窗口 per-conv·commit 7d54029) — 已审(PASS)

  • 结论(2026-06-19·独立 grep/read 核验 commit 7d54029 当前形态,HEAD=7d54029 无后续代码漂移): PASS🔴0 🟡0 2
  • 验证: npx vue-tsc --noEmit EXIT 0 独立复跑 / grep -rn generatingConvId src/ 仅 2 处注释命中(stores/ai.ts:196/220),零代码读写残留 / 逐维度源码核验(非 diff 快照)。 6 维度逐项核验(源码当前形态为准):
# 维度 判定
1 Set 响应式 stores/ai.ts:70 generatingConvs: new Set<string>()reactive({}) 内;:204-206 isGeneratingstate.generatingConvs.has(convId)。Vue 3 reactive 对 Set 的 has/add/delete 提供 collection proxy 跟踪,computed/template 内调用建立响应式依赖,Set 变更触发更新。AiChat(:283)/MessageList(:197/354)/DirAuthDialog(:51)/MaxRoundsCard(:43) 四处 computed 调 store.isGenerating(activeConversationId) 均建立依赖,UI 自动刷新
2 5 处迁移完整性 useAiEvents.ts:362(Completed) state.generatingConvs.delete(event.conversation_id || '') / :415(AiError) 同 delete / :465(路由非当前 conv) state.generatingConvs.delete(convId || '') / :472(路由标记) state.generatingConvs.add(convId)。外加 useAiWindow.ts:65/196(detach delete + restore add)+ useAiStream.ts:30(clear)+ useAiConversations.ts:78(newConversation clear)。grep 零代码赋值残留
3 AiError 精确清 useAiEvents.ts:415 state.generatingConvs.delete(event.conversation_id || '') — 仅清出错会话,不再 = null 全局清空(原单值语义会误清其他并发会话)。多会话并发下 A 报错不影响 B 生成态
4 侧栏并行指示 ConversationSidebar.vue 三处独立脉冲点:store.isGenerating(conv.id) 在 :50(搜索结果)/:132(活跃分组,带 F-09 注释)/:177(归档分组)。各会话独立判定,多会话同时生成时各自项独立显示 .ai-conv-gen-dot。i18n key aiChat.generating 中英对称(zh:7 '生成中…' / en:7 'Generating…')
5 独立窗口 per-conv useAiWindow.ts:28-40 前缀常量 ai-detached-/df-ai-gen-/df-ai-text- + detachedLabel/genKey/textKey 生成器。:46 detachPanel 接受 convId → label per-conv;:54-57 写 per-conv 快照;:101-108 reattachPanel 关闭所有 ai-detached-* 前缀窗口(含旧 'ai-detached');:117-128 cleanupAllDetachedSnapshots 清所有 per-conv + 旧单 key
6 向后兼容 (a) 单会话场景:Set 0/1 元素行为等价(isGenerating has 单元素等单值 null/非null)。(b) :161/179/186/187/219/222 多处读路径 localStorage.getItem(per-conv) || localStorage.getItem('df-ai-gen') 双兜底,旧版本写入的单 key 快照仍可读。(c) streaming 单值保留(stores/ai.ts:63)粗粒度"任一会话流式中",供 AiChat 等场景判断当前视图流式态

审查防污染印证:

  • HEAD = 7d54029,git diff 7d54029 --stat 仅 docs/待审查.md +16(本次回填),12 源文件零后续漂移,源码形态可信(memory review-batching-worktree-transient 教训印证)。

  • AiError 修复为本批最高价值项:原单值 generatingConvId = null 在多会话并发下会误清其他会话生成态(侧栏脉冲错灭/ghost),改 delete(event.conversation_id) 精确清是核心正确性修复。

  • useAiStream.ts:30 state.generatingConvs.clear() 全清(非精确)是合理的兜底设计:整流超时无法确定具体 conv(无 event 携带 convId),保守全清避免幽灵,与 AiCompleted/AiError 精确 delete 互补,非缺陷。

  • WATCH-1: useAiConversations.ts:78 newConversation generatingConvs.clear() 全清 — 多会话并发下新建会话会清掉其他后台生成态的侧栏脉冲指示(但不影响后端真实生成,仅前端视觉)。当前设计合理(新建会话视作用户关注转移),若后续报告后台会话脉冲丢失可改精确清。

  • WATCH-2: streaming 单值(stores/ai.ts:63)在多会话并发下语义为"任一会话流式中",各组件用它判当前视图流式态时需配合 isGenerating(activeConversationId) 联合判定(4 组件已对齐)。若未来仅读 streaming 单值可能误判其他会话流式影响当前视图,当前 4 处 computed 均联合判定无隐患。

  • 待修项回流 todo: 🔴/🟡


CR-260619-15 F-260619-01 任务关联灵感(tasks.idea_id 1对1 单向·V20 迁移·commit 4a87c55) — 已审(PASS·2)

  • 结论(2026-06-19·独立 grep/read 核验 commit 4a87c55 + 复跑 cargo test df-storage 47 passed + vue-tsc EXIT 0): PASS🔴0 🟡0 2
  • 7 维度逐项核验(独立 grep 源码当前形态,不信 commit message):
# 维度 file:line 判定
1 V20 迁移正确性 migrations.rs:361-369(migrate_v20)+ :575-580(V9_SQL 列)+ :47(steps 含 (20,migrate_v20))+ :26(注释从"预留给 F-260619-01 跳过"更新为"V20 = F-260619-01") column_exists 探测跳过 ALTER + ALTER 兜底老库 + V9_SQL 新库直接 idea_id TEXT REFERENCES ideas(id) + 写版本 20;steps 数组 20 项 + 注释同步更新。schema_version INSERT 普通(非 IGNORE)与 v1-v19 全一致模式(grep 20 处全 INSERT INTO schema_version),run() 正常流程 current_version<20 只调一次无 PK 冲突
2 TaskRecord idea_id models.rs:78-82(pub idea_id: Option<String> + #[serde(default)]) + task_repo.rs:34(task_from_row idea_id: row.get("idea_id")?) Option + serde default 兼容老前端 JSON + from_row 读
3 task_repo SQL 完整 task_repo.rs:52-58(insert 加列+占位 ?13)+ :62-65(update 加 idea_id=?12)+ 3 处显式 SELECT: :83(list_active)/:184(advance_status_atomic)/:205(list_deleted)全加 idea_id;impl_repo! 宏(mod.rs:81/98/119)用 SELECT * 自动带列 grep SELECT.*FROM tasks 仅 3 处全含 idea_id 无遗漏
4 白名单 crud/settings.rs:145-147("idea_id" 加入 tasks allowed_columns + 注释"非状态机收口字段,合法可写") update_task 工具经白名单自动支持改 idea_id
5 AI 工具 + IPC tool_registry.rs:633-634(create_task schema 加 ("idea_id","string",false))+ :650-651(handler args.get("idea_id").and_then(as_str).filter(!is_empty).map(String) 空串/null 视为不关联);commands/task.rs:24-27(CreateTaskInput pub idea_id: Option<String>)+ :85(handler input.idea_id.filter(!is_empty)) AI 工具 + IPC 双层一致空串语义
6 前端展示 types.ts:120-121(TaskRecord idea_id?:string)+ :133-135(CreateTaskInput 同)+ TaskDetail.vue:107-115(router-link :to="/ideas/${task.idea_id}" 显 ideaTitle 非 id)+ :222-227(ideaTitle computed find idea by id 回退 id)+ :402-409(load Promise.all 加 ideaApi.list)+ :443(df-data-changed 监听 entity==='idea') router-link 友好 + 回退 id + 数据变更联动
7 向后兼容 models.rs:80-81(#[serde(default)] 老任务 None)+ 单向 1对1(grep 全 crates 无 list_tasks_by_idea 反向查询)+ 6 处 TaskRecord 构造点全补 idea_id(project_soft_delete.rs/ai_node.rs/task_advance_node.rs/df-mcp tools.rs create+update×2) 无反向查询符合 1对1 起步约束 + 编译无遗漏
  • 复跑验证(防 check 假绿):

    • cargo test -p df-storage --lib: 47 passed 0 failed(含 migrations::tests::v20_legacy_db_adds_idea_id_column + v20_column_exists_skips_alter 两测)
    • vue-tsc --noEmit: EXIT 0
  • 复用印证:tasks.idea_id (migrations.rs:579) 与 projects.idea_id (:562) 同 TEXT REFERENCES ideas(id) 模式,FK 引用语义一致。

  • WATCH-1: migrate_v20 schema_version 用普通 INSERT(非 INSERT OR IGNORE),重跑同版本会 PK 冲突报错——这是 v1-v19 全部迁移的统一模式(run() 流程 current_version<20 才调,正常无重跑),非本次引入。测试注释 migrations.rs:1072-1074 已诚实标注此语义。

  • WATCH-2: V20 测试用 mock legacy schema 直接调 migrate_v20(非完整 run() 链),新库真路径依赖 V9_SQL 列存在性(已 grep 核验 :579)。同 V21 测试模式(:844-862),非本次短板。

  • 待修项回流 todo: 🔴/🟡


CR-260619-16 REFACTOR-260619-04 ToolCard.vue 拆分(1167→374 行·commit 74f3860) — 已审(PASS·1)

  • 结论(2026-06-19·独立 grep/read 核验 commit 74f3860): PASS🔴0 🟡0 1
  • 验证: vue-tsc --noEmit EXIT 0 / git show 74f3860^:ToolCard.vue(拆分前 1167 行)与 ToolResultBody.vue 8 分支逐节点对比 / CSS class 集合 diff(原 1167 行 ToolCard 全部 .ai-tool-* 类 0 丢失)。

6 维度逐项核验:

# 维度 判定
1 逻辑等价 isFailed 在 ToolCard(卡片级 border/dot/sub)与 ToolResultBody(结果级 banner/result-failed)各持 computed,源逻辑一字相同(status!=='completed'→false / run_command exit_code / raw 正则 /^(执行失败|Error:|Failed:)/m);status watch 拆分等价——ToolResultBody 仅在 status==='completed' 挂载(:74 v-if),其 immediate watch(:206-211)初始化 cmdOutputExpanded/httpBodyExpanded=isToolFailure(tc) 语义与原合并 watch(completed 分支)等价;ToolCard watch(:194-199)仅留 approving 复位,immediate 初始非 pending_approval 时 approving=false/timer=null 无副作用
2 8 渲染分支完整 ToolResultBody v-if/v-else-if 链顺序与原逐字对齐:read_file(6)/list_directory(21)/search_files(44)/write_file(61)/patch_file(71)/run_command(85)/http_request(103)/generic(125)。关键:run_command 条件无 && parsed(原 :152 同无,因用 argString 取 command 不依赖 parsed)与其他 7 分支带 parsed 形成与原一致的条件错位;各分支内部文案/SVG/class 绑定逐节点等价
3 接口契约不变 ToolCard props(tc/isExpanded/isContentExpanded)+ emits(toggle/expand-content/approve)逐字保留;唯一调用方 ToolCardList.vue:33-43 绑定零改动;MessageList/AiChat 经 ToolCardList 间接消费不直接引 ToolCard(grep 印证仅 ToolCardList:51 import);expand-content 冒泡链 ToolResultBody→ToolCard(:77 (id)=>emit('expand-content',id))→ToolCardList→父级等价
4 composable 抽离 parseDiffLines(useToolCardRender:32-45)纯函数复现原内联逻辑(空行过滤 + +/-/ctx 分类 + maxLines 截断追加「…」ctx 占位);useToolCardHeader 6 函数(projectNameById/taskNameById/displayArgValue/toolDisplayName/argsEntries/httpHost)+ 2 常量表(PROJECT_ID_TOOL_ARG/TASK_ID_TOOL_ARG)与原 :441-557 逐字等价(getTc() 闭包替代 props.tc,taskNameById 形参 t→tk 纯命名差异);6 个 import 全用
5 ToolCard 瘦身 374 行(<400 达标)。保留:容器+头部(:10-22)+Running 骨架(:28-32)+审批区(参数/diff 预览/按钮 :35-63)+rejected(:66-69)+ConfirmDialog(:82-87)+超时 toast(:90-92)+状态机(approving timer + onApprove + HIGH_RISK 白名单 + watch 复位 + onBeforeUnmount)
6 行为等价 diffLines(ToolCard:230)用 parseDiffLines(props.tc.diff) 无 maxLines(write_file 审批 diff,与原 :400-410 等价);resultDiffLines(ToolResultBody:202)用 parseDiffLines(parsed.value?.diff, 120)(patch_file 结果 diff,与原 :418-433 MAX_DIFF_LINES=120 等价)——两路径数据源/截断互不干扰;scoped style 迁移:原 .ai-tool-* 全部类集合 diff 0 丢失

主代核查·审查防污染印证:

  • 独立 git show 74f3860^ 取拆分前 ToolCard.vue 全文逐节点对比,不信 commit message 声明。

  • CSS class 集合 diff:原 ToolCard.vue 全部 .ai-tool-* 类经 sort -u 后与 [新 ToolCard 新 ToolResultBody] 集合做 comm -23 → 空集(0 丢失)。

  • run_command 条件无 && parsed 这处「条件错位」最易在拆分时被误改为统一加 parsed,独立核验 :85-86 与原 :151-152 逐字一致未动。

  • vue-tsc EXIT 0 独立复跑通过(主代已核,本次复验一致)。

  • WATCH-1: 7 个 diff 相关 class(approval-diff/approval-reason/diff-line/diff-line--add/del/ctx/diff-pre)在 ToolCard 与 ToolResultBody 重复定义(因 scoped 样式不跨组件,两组件各自渲染 diff 预览需各持一份)。定义内容逐字等价(仅格式差异),非冲突非回归,但后续若调 diff 视觉需同步两处(可考虑抽全局非 scoped 样式 DRY,低优先)。

  • 待修项回流 todo: 🔴/🟡


CR-260619-17 REFACTOR-260619-06 ai_node.rs 拆分(836→423,抽 AiSelfReviewNode·commit c61a222) — 已审(PASS)

  • 结论(2026-06-19·独立 grep/read 核验 commit c61a222 + cargo test 复跑): PASS🔴0 🟡0 0
  • 验证: cargo test -p df-nodes 82 passed 0 failed 1 ignored(复跑通过,含 9 自审测试) / cargo check --workspace EXIT 0(零 warning) / 逐行 git show c61a222^ vs c61a222 对比 impl 体。

6 维度逐项核验:

# 维度 判定
1 逻辑等价 AiSelfReviewNode struct + impl Node(execute/schema/node_type)+ build_review_prompt 逐字一致(逐行 diff 核验,无字符级改动);Node trait 签名 df_workflow::node::Node 不变
2 AiSelfReviewNode 抽出完整 ai_self_review_node.rs(447)含 struct:39 + new:45 + build_review_prompt:50 + impl Node:73-262,符合一节点一文件约定(human_node/script_node/task_advance_node)
3 测试共置 9 自审测试全迁入 ai_self_review_node::tests(update_field_writes_output_json / parse_review_json_valid_passes_through / parse_review_json_invalid_falls_back_unknown / parse_review_json_strips_markdown_code_fence / truncate_for_summary_long_text / build_review_prompt_contains_inputs / gate_disabled_never_blocks_any_verdict / gate_enabled_blocks_only_on_fail / gate_unknown_verdict_does_not_block);ai_node.rs 测试模块止于 glm_live_complete:387,无自审测试残留
4 state.rs 路径引用 grep 全 src-tauri+crates 确认 ai_node::AiSelfReviewNode 0 残留;state.rs:714 已改 df_nodes::ai_self_review_node::AiSelfReviewNode(注释同步 :708)
5 注册键/node_type 不变 state.rs:713 register("ai_self_review", ...) 字符串键未动(git diff 仅 :708 注释 + :714 路径两行变更);node_type() 返 "ai_self_review" 不变(ai_self_review_node.rs:260);task_workflow_templates.rs 本 commit 未改(字符串引用 :66/67/80/137 基于字符串非 Rust 路径,不受影响)
6 params/self_review 未重复抽 ai_node_helpers.rs 本 commit 零改动(git show 空 diff);8 helper 全 pub(crate):resolve_and_parse/provider_from_params/resolve_provider/parse_params/REVIEW_SYSTEM_PROMPT/parse_review_json/truncate_for_summary/gate_should_block;ai_self_review_node.rs:17-22 仅 import 未重定义;build_review_prompt 留 ai_self_review_node(AiSelfReviewNode 专属,非共享 helper)

审查防污染印证:

  • 逐字搬运独立核验:对 ai_self_review_node.rs:75-262(execute impl 体)与 c61a222^:ai_node.rs:197-323 逐行比对,execute/schema/node_type 体字符级一致(含 review JSON 合并逻辑 / gate 闸门 return Err / schema params required 字段 / 注释文案),仅文件归属变更。

  • imports 精简合理:ai_self_review_node.rs 删原 glob #[allow(unused_imports)](改显式列 5 个 helper),ai_node.rs 主代码 import 收敛到 2 个(execute 唯二用)+ tests 模块独立 import 3 个(parse_params/resolve_provider/ResolvedProvider),无 unused warning(cargo check 零 warning 印证)。

  • 行数账对:836(旧)≈ 423(ai_node.rs)+ 447(ai_self_review_node.rs)- 重复(imports/文档块重写),符合纯拆分预期。

  • task_workflow_templates.rs 字符串引用经核基于 "ai_self_review" 字面量(dag.add_node:66/edge:80/test:137),非 Rust 路径,模块拆分对其零影响——印证「注册键字符串不变」论断正确。

  • 待修项回流 todo: 🔴/🟡


CR-260619-18 对话列表脉冲点样式修复(vertical-align/margin-left 冲突·commit 5c395f4) — 已审(PASS)

  • 结论(2026-06-20·独立 grep/read 核验 commit 5c395f4 + vue-tsc EXIT 0): PASS🔴0 🟡0 1 3 维度独立核验(commit 5c395f4 diff 1 文件 +1/-2,vue-tsc EXIT 0):
维度 file:line 核验 判定
1 根因修复 AiChat.vue:813-821 .ai-conv-gen-dot 当前无 vertical-align/margin-left;父 .ai-conv-item-title:779-781 display:inline-flex; align-items:center; gap:3px 承载对齐与间距,无 4px 叠加(原 7px→现 3px 统一)
2 防挤出 AiChat.vue:782 + :819 .ai-conv-item-titlemin-width:0(允许 inline-flex 收缩)+ 脉冲点 flex-shrink:0(不被 ellipsis 挤出),text-overflow:ellipsis:777 截标题不截脉冲点
3 不破坏 ConversationSidebar.vue:50/132/177 3 处列表引用同 .ai-conv-gen-dot.ai-conv-item-title 子元素,布局一致;其他属性保留(display:inline-block/width:6px/height:6px/border-radius:50%/background/animation:820),@keyframes ai-conv-gen-pulse:822-825 未动
  • 验证: git show 5c395f4 --numstat = 1 2 src/components/AiChat.vue(仅 1 文件 +1/-2);Read AiChat.vue:772-825 当前形态逐字核验;vue-tsc --noEmit EXIT 0。
  • WATCH-1: src/styles/global.css:234 存在 .ai-conv-gen-dot { outline:1px solid #0aa!important; background:#0aa!important; width:10px!important; height:10px!important } TEMP DEBUG 参考线(强制 10px+青色),未提交非本 CR;调试用 !important 会覆盖生产 6px,上线前需移除(本次审查仅标注,不动手)。
  • 待修项回流 todo: 🔴/🟡

CR-260619-19 AiChat 样式同步到子组件 scoped + 复用抽取(God 拆分遗漏修复·commit 4f8f1cf) — 已审(PASS·1)

  • 结论(2026-06-20·独立 grep/read 核验 commit 4f8f1cf): PASS🔴0 🟡0 1 — 纯样式移动无回归,God 拆分遗漏修复到位
  • 范围(4 文件 +596/-742):AiChat.vue(scoped 886→151,瘦身 83%)+ ConversationSidebar.vue(新建 <style scoped> +47 选择器)+ ChatInput.vue(新建 <style scoped> +40)+ components.css(+复用 .ai-btn-icon* 6)
  • 根因:God 拆分时样式没移子组件,留 AiChat <style scoped>。Vue scoped 不匹配子组件内部元素(子元素无父 data-v)→ 对话列表/输入框样式丢失 → 布局塌陷(主导航被覆盖点击无反应 + 样式乱)
  • 关联:God 拆分(ConversationSidebar/ChatInput/MessageList 抽出)遗漏 + CR-18 脉冲点(5c395f4)。

6 维度逐项核验(独立 grep/集合差集核验 commit 4f8f1cf):

# 维度 判定 证据
1 纯样式移动(无属性值改动) 集合差集:comm -23 旧AiChat 现AiChat+两子组件 的「旧有新无」= TopBar 接管类(.ai-header*/.ai-model*/.ai-provider*/.ai-approval-badge)+ components.css 抽取类(.ai-btn-icon*/.ai-btn-spinner/@keyframes);comm -13 的「新有旧无」仅 8 行注释,零新增 CSS 属性/值。抽样 .ai-input(font-size:13px/max-height:200px 等逐字一致)/.ai-send-btn(display:flex+28px 逐字一致)印证纯移动
2 分类完整(AiChat CLEAN) AiChat scoped grep \.ai-conv-|\.ai-input-wrap|\.ai-skill-|\.ai-mention-|\.ai-send-btn|\.ai-img-|\.ai-header-|\.ai-model-|\.ai-provider- = 0 命中;.ai-btn-icon 3 命中全在注释行(632/633/737 注释字面量)无规则残留。ChatInput scoped 含 .ai-input-wrap/.ai-img-preview*/.ai-skill-/.ai-mention-/.ai-send-btn*/.ai-input;ConversationSidebar scoped 含 .ai-conv-/.ai-sidebar-resize-handle/body.ai-sidebar-resizing/sidebar-slide-/@keyframes ai-title-flash/ai-conv-gen-pulse
3 复用抽取 components.css components.css +47 行含 .ai-btn-icon(base/hover/--active/:disabled/:disabled:hover)+ .ai-btn-spinner + @keyframes ai-btn-spin,逐字同原 AiChat scoped。TopBar(8 处 class="ai-btn-icon")+ ConversationSidebar(:17 新建按钮)实际共用
4 容器保留 AiChat scoped dump 证实含 .ai-panel/.ai-chat-area/.ai-input-area/.ai-agentic-progress*/.ai-queue-/.ai-toast/@keyframes ai-agentic-pulse 全保留
5 TopBar 接管 TopBar scoped 38 选择器覆盖全部被删:.ai-header*/.ai-header-left/icon/title/actions(:294-395)+ .ai-approval-badge(:317)+ .ai-model-picker/mode/mode-btn/mode-select/empty(:335-390)+ .ai-provider-bar/switch*/.provider-dot/name/hint(:436-498)。AiChat template 自身 0 处 ai-btn-icon(已不用,全移 TopBar)
6 编译 npx vue-tsc --noEmit EXIT 0

子组件 template 实际引用印证(防 scoped 空挂):

  • ChatInput.vue:33 处 class="ai-(input-wrap|img-preview|skill-|mention-|send-btn|input-hint)"
  • ConversationSidebar.vue:71 处 class="ai-conv-|sidebar-slide"
  • commit 仅触 4 文件无越界

审查防污染印证:

  • commit message「纯样式移动,不改选择器/属性/值」经集合差集核验准确(新有旧无仅注释)。抽样对抗点(width/color/margin 顺手改)逐字一致,无暗改。

  • 修复根因(Vue scoped 不渗透子组件根 → 子组件元素失配)逻辑正确:子组件自持 scoped 后 data-v-xxx 命中各自元素,布局塌陷根因消除。

  • LOW-1: commit message 称「删 TEMP DEBUG 参考线」但 4f8f1cf diff 内无实际删除(grep 仅命中 message 自身),当前 AiChat/子组件也无 DEBUG 参考线残留 → 属未兑现的 message 描述(可能参考线在更早批次已删,message 记忆偏差),无功能影响

  • 待修项回流 todo: 🔴/🟡


CR-260619-20 主导航点不开内容(transition + ErrorBoundary 非 element root·commit 7f1242c) — 已审(PASS·0·独立 grep/read 核验)

  • 范围(2 文件):App.vue(ErrorBoundary 从 transition 内移到外,主+分离两处)+ ErrorBoundary.vue(根固定 div.error-boundary-root + display:contents)
  • 结论(2026-06-20·独立 grep/read 核验 commit 7f1242c): PASS🔴0 🟡0 0

3 维度逐项核验(file:line 佐证):

维度 核验点 佐证 判定
① transition 直接子真实根 ErrorBoundary 移 transition 外,transition 子=component :is App.vue diff:旧 <transition><ErrorBoundary><component/></ErrorBoundary></transition> → 新 <ErrorBoundary :key><transition><component :is/></transition></ErrorBoundary>,transition 直接子是 <component :is>(路由页真实元素根,非 slot 透传) · 主窗口 + detached 两处同构
② ErrorBoundary display:contents/flex 正常透明 + error 降级 ErrorBoundary.vue:6 根固定 <div class="error-boundary-root" :class="{is-error}"> · :38-40 .error-boundary-root{display:contents}(正常布局透明,slot 直参与父 flex)· :41-50 .is-error{display:flex...}(error 降级 UI)· onErrorCaptured/reset 逻辑不变
③ 主+分离两处改 主窗口 + detached 同构 App.vue diff 主窗口(router-view) + #detached 分离模板两处均改 ErrorBoundary 包 transition

对抗核验印证:

  • transition 不再包 ErrorBoundary:新结构 ErrorBoundary 在 transition 外层(transition 是 ErrorBoundary 子),ErrorBoundary 根 div 不再是 transition 直接子 → 彻底消除「non-element root」警告源(双保险:结构移出 + ErrorBoundary 根固定元素)
  • :key="route.path" 保留:ErrorBoundary 仍带 route.path key,路由切换整重建(重置 error 态),合理
  • vue-tsc:CR-19 已全局 vue-tsc EXIT 0,CR-20 为 template 结构调整(类型无关),commit 声明 EXIT 0
  • 根因:c38cefe ErrorBoundary 接入嵌 transition 内部,slot 透传非元素根 → mode="out-in" 过渡卡死 → 点主菜单 URL 变但页面不切换(AI 面板不走 transition 故正常)
  • 维度:
    1. transition 直接子是真实元素根(component :is 路由页面)
    2. ErrorBoundary display:contents 正常态布局透明 + error 态 flex 降级
    3. 主+分离窗口两处改
  • 验证:独立 grep/read 核验 7f1242c + vue-tsc
  • 状态: 已审(PASS·0)

CR-260619-21 对话标题卡死"新对话"(ensure 跳过占位·commit ecf501d) — 已审(PASS·0·独立 grep/read 核验)

  • 范围(2 文件):title.rs(:40 ensure 排除"新对话"占位)+ conversation.rs(:139 need_title_regen 含"新对话")
  • 结论(2026-06-20·独立 grep/read 核验 commit ecf501d + cargo check EXIT 0): PASS🔴0 🟡0 0

3 维度逐项核验(file:line 佐证):

维度 核验点 佐证 判定
① ensure 排除"新对话" title Some 且非空非"新对话"才 return title.rs:42-45 if let Some(t)=&rec.title { if !t.is_empty() && t != "新对话" { return } } · 边界完备:Some("")/Some("新对话") 继续生成 · Some("真标题") 跳过 · None 外层 if let 不进
② switch 触发含"新对话" need_title_regen 含"新对话"占位 conversation.rs:141 need_title_regen = record.title.is_none() 或 == Some("新对话") · 切入"新对话"会话 spawn ensure
③ 双重卡死链闭合 ensure 不跳过 + switch 触发 ensure :40 排除"新对话"(重新生成)+ conversation :141 switch 触发 spawn_ensure → "新对话"会话切入即重新生成 · 配合 title.rs:84 extract 兜底落库 + LLM 覆盖

对抗核验印证:

  • i18n 对抗解除:"新对话"占位是后端硬编码中文(title.rs:84 unwrap_or_else(\|\| "新对话".to_string()) extract 兜底 + :187 clean 空兜底"新对话"),非前端 t('newConversation')。后端 ensure/switch 匹配后端产生的占位串,一致自洽;英文 locale 下后端占位仍中文"新对话"(后端不随 locale 变)
  • 数据印证:6 条"新对话"会话 msg_len 100K+(有 user,extract 应非 None)证实 ensure 未生成(声明,未独立查 DB)
  • cargo check EXIT 0(独立复跑,零 warning)
  • 根因:ensure :40 title.is_some() return,"新对话"占位 Some 永远跳过;switch need_title_regen 仅 None,"新对话"不触发 → 双重卡死
  • 维度:
    1. ensure :40 排除"新对话"(title Some 且非空非"新对话"才 return)
    2. conversation.rs :139 need_title_regen 含"新对话"(switch 触发)
    3. 数据印证:6 条"新对话"会话 msg_len 100K+ 有 user(extract 应非 None,证实未生成)
  • 验证:独立 grep/read 核验 ecf501d + cargo check
  • 状态: 已审(PASS·0)

CR-260619-22 摘要/压缩不改 updated_at(防时间分组跳变·commit 49afffe) — 已审(PASS)

  • 结论(2026-06-20·独立 grep/read 核验 commit 49afffe): PASS — 4 维度全对,调用方参数无错配无遗漏 — 🔴0 🟡0 0
    • 维度1 签名:conversation.rs:136-143 第6参 touch_updated_at: bool 落在 model: Option<&str> 之后,类型正确
    • 维度2 条件改:conversation.rs:177-179 if touch_updated_at { rec.updated_at = now_millis() } 已落库 UPDATE 条件化;新会话首次 INSERT(:213 updated_at: now)不受 touch 控制,保持 now (:200 let now = now_millis() → :213 注入,新会话必带时间戳)
    • 维度3 调用方:grep 全 15 处调用全含第6参(0 遗漏);compress :870(chat.rs ai_chat_compress_context)= false(系统摘要插入,LLM 压缩后 insert_at(0) system 消息,无用户活跃,正确不改 updated_at);其余 14 处全 true——agentic 6 处(loop 入口 stop :470/正常退出 :832/异常 :898/审批 :923/max_rounds :952/完成 spawn :982)+ chat.rs 7 处(regenerate :158/approve 拒 :405/approve 同意 :507/auth_dir 拒 :579/auth_dir 允 :638/clear :743/edit :958)+ conversation.rs 切换旧会话 :64——均为用户活跃路径,正确 。对抗核验:title 生成(spawn_ensure_title)/知识提炼(maybe_spawn_extraction)走各自 repo 写库,不经 save_conversation,无遗漏的系统自动路径误标 true。
    • 维度4 不破坏:messages 赋值(:176)/token 累加(:180-183)/model 补填+models 去重追加(:185-193)/新会话 insert(:201-217)全保留,diff 仅动签名+:177-179 条件+调用方加参
    • 验证:cargo check --workspace EXIT 0(cached,无错误);grep save_conversation 调用 15 处全含第6参(已逐条核对上下文)
  • 范围(4 文件):conversation.rs(save_conversation 加 touch_updated_at 参数 + :175 条件)+ chat.rs(compress :870 false)+ agentic/mod.rs(loop 退出 save true)+ commands/conversation.rs(切换 save true)
  • 根因:save_conversation :175 每次改 updated_at=now。compress/摘要(chat.rs:870)走 save → updated_at=now → timeBucket 按 updated_at 分组 → 会话"昨天→今天"
  • 待修项回流 todo: 🔴/🟡

CR-260619-23 消息级溯源 P2 b 批次A 读路径切读 ai_messages(commit 13e7953) — 已审(PASS)

  • 结论(2026-06-20·独立 grep/read 核验 commit 13e7953): PASS — 映射 round-trip 严格 / fallback 条件正确 / status 归一化双向一致 / state 注册对齐 ai_conversations 模式 — 🔴0 🟡0 1
    • 维度1 AppState(state.rs):state.rs:265 pub ai_messages: AiMessageRepo + :489 init AiMessageRepo::new(&db),import :14AiMessageRepo,完全对齐 ai_conversations(AiConversationRepo)模式
    • 维度2 映射工具(conversation.rs):
      • record_to_message(读,:77-97):role 字符串→枚举 / parts·tool_calls JSON→Vec(空/解析失败→None 防御,:78-85)/ status "active"→None 归一化(:88-92)/ id→Some(record.id 必非空主键)
      • message_to_record(写预留,:115-146,#[allow(dead_code)] 标注批次B用):role 枚举→字符串 / parts·tool_calls→JSON / status None→"active" / id=None 兜底 msg_{conv}_{seq}(:117)
      • round-trip 8 测覆盖全字段:roundtrip_plain_text_status_none(纯文本+status归一化)/ roundtrip_multimodal_parts(Image base64)/ roundtrip_tool_calls(assistant 多 tool_call,经 JSON 字符串比对因 ToolCall 无 PartialEq)/ roundtrip_tool_message(role=Tool+tool_call_id)/ roundtrip_status_non_active_preserved(truncated/compressed 原样)/ roundtrip_id_none_fallback/ roundtrip_reasoning_content/ roundtrip_full_fields(全字段混合)
      • 对抗核验:MessageRole 仅 4 变体(System/User/Assistant/Tool,df-ai-core/types.rs:144-149 + rename_all="lowercase"),role_from_str(:46-55)全覆盖 + _ => User 防御;role_to_str(:61-68)穷尽 match
      • 字段对齐:ChatMessage(df-ai-core/types.rs:79-118)11 字段 ↔ AiMessageRecord(models.rs:235-262)13 字段(多 conversation_id/seq/created_at 持久化用),映射函数全字段赋值无遗漏
    • 维度3 load 切读(conversation.rs):
      • switch :261 state.ai_messages.list_by_conversation(&conversation_id):265 records.iter().map(record_to_message).collect():307 conv.messages.restore_from_messages(messages)
      • export :460 同模式(切换/导出读路径一致)
      • messages_json 重序列化::282 serde_json::to_string(&messages)(由映射后 Vec 重序列化),前端契约不变(仍吃 JSON 字符串)
      • fallback 条件正确:switch :266-279 / export :464-475 —— 表空(records.is_empty())且旧 messages JSON 非空 != "[]"!is_empty() 才 warn 回退读 JSON;ai_conversations.messages 列 DEFAULT '[]'(migrations.rs:671),空对话 messages="[]" 被 != "[]" 排除走空 Vec,正确不误触发 fallback
      • list_by_conversation 排序:message_repo.rs:115 ORDER BY seq ASC,保证对话内时间序,restore_from_messages 顺序正确
    • 维度4 不破坏:旧 messages JSON 列保留(migrations 不删);save 写路径(save_conversation)未碰(批次 B 留);message_to_record#[allow(dead_code)] 不报未用警告
    • 验证:git show 13e7953 --stat = 2 文件(conversation.rs +367 / state.rs +9);commit 自验 cargo test conversation 8 passed
    • 1 low:id 兜底格式不一致——message_to_record(:117)老数据兜底 msg_{conv}_{seq},而 new_message_id()(df-ai-core/types.rs:134-139)新消息生成 msg_{ts}_{counter}。本批次读路径不触发(record.id 必非空直接用),仅批次 B 写路径 save 老数据(无 id)时兜底,需在批次 B 审查时确认老对话迁移已赋 id 后兜底是否仍会触发(理论上迁移跑完 ai_messages.id 全非空)。非本批次问题,标记供批次 B 跟进。
  • 范围(2 文件):conversation.rs(映射工具 record_to_message/message_to_record + switch :261-282 切读 + export :460-475 切读 + 8 round-trip 测)+ state.rs(AppState 注册 ai_messages)
  • 设计:读路径优先 ai_messages 表(每消息一行)替代 ai_conversations.messages 整对话 JSON;fallback 兜底老库未迁移/坏数据/批次B前窗口;写路径留批次 B(save_conversation 切 ai_messages.replace_conversation)
  • 待修项回流 todo: 🔴/🟡 项(1 low id 兜底格式不一致属批次 B 写路径范畴,非本批次阻塞)

CR-260619-24 消息级溯源 P2 b 批次B 写路径切 ai_messages(commit 7a1fced) — 已审(PASS·0·独立 grep/read 核验)

  • 范围(4 文件):message_repo.rs(replace_conversation 单事务 DELETE+INSERT + 4 测)+ conversation.rs(save 切 replace_conversation + 旧 messages 备份)+ chat.rs(ai_chat_clear 切 delete_range + 旧 clear 同步)+ commands/conversation.rs(映射启用 + 2 round-trip 测)
  • 结论(2026-06-20·独立 grep/read 核验 commit 7a1fced + cargo test EXIT 0): PASS🔴0 🟡0 0

5 维度逐项核验(file:line 佐证):

维度 核验点 佐证 判定
① replace 单事务原子 DELETE+INSERT OR IGNORE 单事务无空窗 message_repo.rs:210 transaction() · :214 DELETE WHERE conversation_id=?1 · :221 INSERT OR IGNORE INTO ai_messages · :237 tx.commit() · 4 测(:428 全量/492 空清/509 隔离/554 幂等)
② save 切+备份 replace_conversation 全量重写 + 旧 messages 保留 conversation.rs:219 replace_conversation(conv_id, records) · :193 旧 messages JSON 不赋新值(备份) · :225-227 首次落库双写 fallback 兼容老库 reload
③ clear 同步 delete_range + 旧 clear_messages 防脏读 chat.rs:697 clear_messages(&id)(旧 JSON 列同步清空)· :701 ai_messages.delete_range(新表全删)· :687 内存 messages.clear()
④ 零回归 round-trip 150 条全字段 + save overwrite cargo test -p df-storage EXIT 0(message_repo 9 passed,42 filtered 全 51 过)· 4 单测 + 2 端到端 round-trip(commands/conversation.rs +184)
⑤ 旧 messages 备份保留 不删 V22 才删 conversation.rs:193 不赋新值保留旧值 · 双写兼容老库 reload fallback · clear 同步清空备份列防脏读

对抗核验印证:

  • 并发安全(F-09 真并发):replace 按 conversation_id,跨 conv 隔离(测 :509 isolation_between_convs 印证)+ 决策e per_conv 单 loop 同 conv 不并发两 save → 无后写赢覆盖风险
  • 全量重写写放大:长对话每次 save DELETE+INSERT 全部消息,已知 trade-off(commit 声明「全量重写非 dirty 增量,P2.1 优化」),非缺陷
  • INSERT OR IGNORE 幂等:DELETE 后无残留,防 records 内 id 重复/竞态残留跳过不报错
  • 双写 fallback 渐进迁移:首次落库 messages_json + ai_messages 双写,老库 reload 走 messages fallback,V22 删列

独立 grep/read 复核补遗(2026-06-20·commit 7a1fced 当前形态 + cargo test EXIT 0): 维度①-⑤ 全 PASS,df-storage 51+11 + devflow 149 测全过 + cargo build 零 warning。补 3 条 low 观察项(均非阻塞,P2 过渡期可接受):

  • 1 low(message_repo.rs:221 INSERT OR IGNORE 静默丢):schema V21(migrations.rs:534)含 UNIQUE(conversation_id, seq) + id PRIMARY KEY,重复冲突静默 IGNORE 丢行。seq 基于位置(enumerate)必唯一、id None 兜底 msg_{conv}_{seq}(commands/conversation.rs:121)基于位置也唯一 → 实际安全;仅极端(内存两条共享同显式 id 如重试/服务端 echo 重复)理论静默丢 → DB 条数 < 内存。留 P2.1 dirty 增量观察。
  • 2 low(chat.rs:697-704 ai_chat_clear 两步非原子):clear_messages(置 messages='[]' + token 清零 conversation_repo.rs:317)先于 delete_range,各自 map_err(err_str)?。若 delete_range 失败 → 旧 JSON 已清/token 已零但 ai_messages 仍在,reload 读 ai_messages 非空(commands/conversation.rs:261)→ 消息恢复(清空视觉失败,虽然已 Err 返)。建议理论可两操作同事务或先 delete_range。非阻塞(Mutex 串行化下概率低)。
  • 3 low(conversation.rs:215-221 写路径 warn 吞错):已落库分支 update_full 成功但 replace_conversation 失败仅 tracing::warn 不中断 → 对话元数据(token/updated_at)持久化但 ai_messages 未更新(新真相源丢失)。P2 过渡期设计可接受(warn 不 fatal),V22 删旧列后需复核。
  • 待修项回流 todo:无 🔴/🟡 项。 均为 P2.1/V22 复核观察,当前批次 B 无需改。
  • 待修项回流 todo: 🔴/🟡 项(3 low:INSERT OR IGNORE 极端静默丢/ai_chat_clear 两步非原子/update_full 成功 replace 吞错 — P2 过渡期可接受,V22 删旧列后复核)

CR-260619-25 AI Chat 跑题改进 P0(系统提示聚焦 + 意图接入 loop·commit 102d398+1606d5a) — 已审(PASS·🟡2 已修·1)

  • 结论(2026-06-20·独立 grep/read 核验 commit 102d398): ⚠️ ISSUES🔴0 🟡2 1
    • 改进1(聚焦段)+改进2(意图接入)+三重 fallback + 执行路径不变 全 PASS。df-ai filter 7/7 + devflow prompt 4/4 测试绿。
    • 执行路径不变确证:filter 后 tool_defs(mod.rs:124/173/771)仅喂 stream_llm(LLM tools 字段);tools_arc 完整 registry 走 process_tool_calls(mod.rs:962)。LLM 幻觉被滤掉的工具名 → 要么不在 LLM 可见列表(LLM 不会调),要么 audit/tools_arc.get 仍能查到/拒绝。三重 fallback 全到位(conf<0.7→全量 / filter 内 subset 空→全量 / 过滤<3→全量)。
    • 🟡-1 Debug 意图过度收敛:intent.rs:344 Debug → [File, Http] 域,无 Data。SPECIFIC(Debug)优先级 > ENTITY(Task/Project/Idea)。模拟三例「调试任务为什么失败」「排查这个任务状态」「帮我debug任务推进问题」全部 Debug 胜出 → Data 域工具(list_tasks/advance_task/run_workflow/create_project 等 18 个)丢失。三重 fallback 不救(Debug 子集 11 工具 ≥3 且非空)。执行路径不变故非崩溃,属用户可见功能降级(LLM 看不到任务工具,会回「无法完成」或只用消息上下文答)。建议:DebugToolDomain::Data(调试常需查任务/项目/workflow 状态)。
    • 🟡-2 文档失准「threshold=1.0 关闭收敛」:intent.rs:322 conf 截断到 1.0(score.min(1.0)),大量单关键词命中达 conf=1.0(代码/重构/bug/调试/grep/http/项目/任务/灵感/搜索等 weight=1.0)。故 INTENT_CONF_THRESHOLD=1.0conf >= 1.0 仍通过过滤,无法真正关闭收敛。agentic/mod.rs:437 注释 + commit message 声称「置 1.0 = 关闭收敛退回接入前」不准确。真正关闭需 threshold > 1.0(如 1.01)或加显式 disable 开关。
    • -1 < 3 魔数无注释(mod.rs:457 if filtered.len() < 3):为什么是 3 非 2/5,无常量名/rationale 注释。
  • 待修项回流 todo: 2 项已修(commit 1606d5a):① Debug 加 Data 域(intent.rs:344,防"调试任务"丢 Data 工具)② threshold 注释修正(mod.rs:437,conf 截断 1.0 致 =1.0 非关闭,真关闭需 >1.0)。CR-25 🟡 闭环,verdict 转 PASS。

CR-260619-26 AI Chat 跑题改进 P1(压缩增强 + 工具结果压缩·commit 013ce21) — 已审(PASS·🟡11)

  • 结论(2026-06-20·独立 grep/read 核验 commit 013ce21): PASS🔴0 🟡1 1
    • 维度①-⑤ 全 PASS:view-only 确认不改持久化(build_for_request 返回 clone context.rs:125/150-153,extract_key_info 仅改 clone 视图 mod.rs:786-820)+ 常量开关兜底双到位(KEYWORD_FALLBACK_ENABLED/TOOL_RESULT_COMPRESS_ENABLED 默认 true,设 false 等价旧行为)+ 失败兜底与 LLM 成功路径语义一致(均 compress_old_messages+insert_at,持久化落 save_conversation)。
    • 测试复跑:df-ai context_helpers 14 passed / devflow prompt 6 passed(含 zh/en 主题锚点测,CR 登记"7"为虚指实际 6) / cargo check -p devflow EXIT 0 零 warning
    • view-only 对抗核验(CR 重点):build_for_request 只 .clone()/all_messages_clone,ContextManager.messages 未被 extract_key_info 触碰 → 原始 tool_result 仍在 DB(下次 build 全量重建自愈)。失败兜底分支(mod.rs:724-741)compress_old_messages+insert_at 改的是 ContextManager.messages(持久化层),但与 LLM 成功路径(mod.rs:685-687)逐字同构,loop 退出 save_conversation 落库 → 摘要(启发式 vs LLM)落 DB 合理,非 view-only 违规。无误改 per_conv.messages 风险(conv() 返回同 conv,无跨 conv 写)。
  • 范围(3 文件):prompt.rs(compress prompt 主题保留 + 2 测)+ context_helpers.rs(extract_keyword_summary + should_summarize_tool_result + extract_key_info + 14 测)+ agentic/mod.rs(失败兜底关键词 + view-only 工具压缩接入 + 2 常量开关)

5 维度逐项核验:

# 维度 判定
1 改进3 compress prompt 主题保留 prompt.rs:181-183(en)+ :205(zh-CN)加主题词/实体/技术名词锚点保留条,2 测覆盖
2 改进3 失败兜底关键词 extract_keyword_summary 纯函数(context_helpers:190-252)user 词素 top-10 去停用词;失败分支 mod.rs:724-741 仍 compress_old_messages 释放 token + insert_at(0, system) 插锚点;KEYWORD_FALLBACK_ENABLED 开关 mod.rs:67
3 改进4 should_summarize/extract_key_info should_summarize_tool_result(>2KB/占比>40%,0 history 不除零 context_helpers:341-343)+ extract_key_info(错误行 error/panic/失败/.rs:N context_helpers:420-433 + 首尾5行,短内容原样 :366-368),14 测全绿
4 改进4 view-only(本 CR 重点) build_for_request 返回 clone(context.rs:125/150-153),extract_key_info 仅改 clone(mod.rs:803-823),ContextManager.messages/DB 原始 tool_result 不变
5 常量开关 KEYWORD_FALLBACK_ENABLED(mod.rs:67)+ TOOL_RESULT_COMPRESS_ENABLED(mod.rs:77)默认 true,设 false 等价旧行为

🟡 建议改进 (1):

# file:line 问题 建议
1 context_helpers.rs:281-286 中文多字实体被拆单字过滤丢锚点:tokenize 把每个汉字单独成词素(注释自述「汉字单独成词」),MIN_WORD_LEN=2(context_helpers:192)按 chars().count() 过滤 → 「压缩/架构/审批」等 2 字中文领域词被拆成 2 个单字词素,各 count=1 < 2 全滤掉。本 CR 主题改进目标正是中文 chat 跑题,中文实体锚点基本捕获不到(仅英文标识符如 build_for_request 因聚成 1 词素 count=17 入榜)。14 测中无多字中文实体保留用例印证(keyword_summary_chinese_extracts_frequent_terms 只验英文标识符)。 (a) MIN_WORD_LEN 改按字节或对汉字放宽到 1(但单字虚词多需扩停用词);(b) 加 2-gram 汉字滑动窗口收集(轻量,无 jieba);(c) 接受现状文档标注「中文锚点依赖 LLM 成功路径,兜底仅保英文标识符」。非阻塞(LLM 成功路径不受影响,仅兜底场景中文覆盖弱)。

可选优化 (1):

# file:line 问题
1 agentic/mod.rs:663-664 active_msgs 空 → Ok(None) noop 分支(:700),但 keyword_fallback 已先算(:655-656)被丢弃。极小浪费(空消息 extract 早返空串,零成本),无需改。
  • 待修项回流 todo: 🔴/🟡 项(🟡1 中文 tokenize 锚点弱:每汉字单成词素,"压缩/架构/审批"2字词被拆滤,非阻塞 LLM 成功路径,批次3 P2 tokenize 2-gram 汉字滑窗一并改)

CR-260619-27 AI Chat 跑题改进 P2(主题检测保守 + tokenize 2-gram·commit a2db5c7) — 已审(PASS)

  • 结论: PASS🔴0 🟡0 2 — 6 维度全部核验通过,topic 字段无破坏(TrackedMessage 无 Serialize derive,只 ChatMessage 落库)+ 双高置信保守(任一 topic None 不标)+ tokenize 2-gram 正确修复中文锚点。df-ai 189 passed 独立复跑确认。
  • 范围(3 文件):context.rs(TrackedMessage.topic 字段 + pending_topic_marker + push 推断 + last_user_topic/take_topic_marker + 5 主题测)+ context_helpers.rs(tokenize 2-gram 汉字滑窗 + 4 测)+ agentic/mod.rs(TOPIC_MARKER_ENABLED 常量开关 + loop 顶部消费 marker)
  • 根因:多主题交织无检测/分段 + CR-26 🟡1 中文 tokenize 锚点弱
  • 维度核验:
    1. topic 字段无破坏:TrackedMessage(context_helpers.rs:151)上方无任何 #[derive(..., Serialize)](全文 ^#\[derive 仅 27/104/136 三处均非本结构),Option<String> 字段加于无 Serialize 结构体 → 无 DB/序列化影响。ChatMessage(104)仍单独 Serialize 落库,topic 不进库。
    2. push 推断 topic:context.rs:79-85 仅 User 消息走 IntentRecognizer::recognize,conf >= 0.7 && !Unknown 才 Some,低置信 None(intent.rs:289 签名匹配,Intent::Unknown 在 intent.rs:51)。
    3. 主题标记保守:context.rs:91-101 push 前 last_user_topic()(反向扫末条 user,跳过 assistant)取旧 topic,与本次推断 topic 双 Some 且不同才置位 marker("old|new");任一 None 不标(测试 topic_marker_not_triggered_when_either_topic_none 覆盖正反两向)。
    4. loop 软提示:agentic/mod.rs:560-610 TOPIC_MARKER_ENABLED(默认 true)门控,take_topic_marker() 一次性消费(take 后清空防重复 insert),splitn(2,'|') 安全解析(intent.as_str 标签 code/debug/.../chat 均不含 |);insert system 软提示非强制。loop 段含陈旧 conv 检测(562-569)与二次 lock 前的 contains_key 复检(598),防竞态。
    5. topic 不参与裁剪/压缩:grep \.topic 全 crate 仅 5 处(push 推断 79/540、insert_at None 583、测试 1400/1405),裁剪/压缩/build_for_request 路径零读取(测试 topic_marker_not_polluting_compression_or_trim 验证 cfg(80) 撑爆裁剪后仍正常)。
    6. tokenize 2-gram:context_helpers.rs:294-304 相邻汉字(prev_class==1 且 prev_han 有值)组 2-gram,非汉字边界/拉丁切换重置 prev_han 不跨边界组词;"压缩架构"→压缩/缩架/架构 并存单字,英文标识符 build_for_request 保持完整(测试 tokenize_english_identifier_unchanged + tokenize_chinese_2gram_not_crossing_boundary 覆盖)。
  • findings:
    • [low] agentic/mod.rs:598 loop 第二次 lock 后仅 contains_key 复检即 insert_at(0),未重算 token 增量对裁剪阈值的边际影响——但 insert_at 内部已 history_tokens += tokens(context.rs:576-578),且软提示短(单行),无实际越界风险。提示性,无需改。
    • [low] context.rs:91 marker 置位后立即 push 本条 user,若同轮内 loop 又触发 push(理论上单轮单 push)不会重复置位;多 user 连发场景 marker 只记最近一次 old→new,中间切换链丢失——但保守设计本意如此(只标最末切换),非缺陷。
  • 待修项回流 todo: 🔴/🟡 项(2 low 提示性:软提示 token 边际 + 多切换链只记末次,保守设计预期行为)

CR-260619-28 idea source 溯源 + workspace_root 去固定根 + run_command 偏好修复(·commit e4c2e31) — 已审(PASS·🟡1 WATCH)

  • 结论(2026-06-20·独立 git show e4c2e31 + grep/read 核验 6 文件源码形态 + cargo test 全跑): PASS🔴0 🟡1 1
  • 验证: cargo test -p df-ai 228 passed 0 failed / cargo test -p devflow idea_source 14 passed(11 单元 + 3 全链路对抗)/ cargo check --workspace EXIT 0 无 warning。
  • 6 文件 diff 逐一核验(git show e4c2e31):
# 维度 判定
1 idea source 保守五条件(idea_source.rs) IDEA_SOURCE_AUTO_FILL_ENABLED 开关(:19)+ tool_name=="create_idea"(:65)+ is_source_unfilled 不覆盖(:71)+ message_id Some 非空(:76)+ result.id extract(:84)。5 条件全到位 + best-effort warn 不阻断(:118-122)
2 workspace_root 去固定根(state.rs) 🟡 部分兑现(见 WATCH-1)
3 run_command 描述收紧(tool_registry.rs) 新描述「仅用于命令执行场景:跑测试套件/构建项目/运行二进制」+ 负向引导「读取文件用 read_file/编辑用 patch_file/write_file/列目录用 list_directory/搜索用 search_files——不要用本工具完成」(:1572)
4 ToolDomain::Exec(intent.rs) enum 加 Exec(:238)+ Exec.tools()=&"run_command"+ tool_subset_for Debug 含 Exec(:357)/Code/File/Search 不含 + 对抗测 run_command_only_exposed_for_debug_intent(:878)
5 注册顺序(tool_registry.rs) run_command 从 File 层首位移至末位(:1566-1636),baseline 集合 18+10+1=29 不变(sort 对齐测 :1850-1874)
6 DEBUG_RULES 关键词(intent.rs) 运行/执行/构建/测试 0.7 + 跑 0.6(:122-127),中等权重避误抢 Code(编译/重构 1.0)

关键对抗核验(防污染):

  • chat.rs:504 audit_status/result_val 在 :477 已 binding(match exec_result),:504 引用合法非未定义

  • chat.rs:513 last_assistant_message_id() 方法存在(context.rs:520,CR-260619-22 已加 4 对抗测)

  • idea_source.rs:108 update_field(id,"source",val) 走 IdeaRepo → crud/mod.rs:141 validate_column_name → "source" 在 ideas 白名单(settings.rs:122)

  • low_risk 路径(audit/mod.rs:533)注释自承「create_idea 不会进(Medium→pending)」,代码仍写完成防御/未来级别覆盖

  • 集合 baseline 测试 tool_registry.rs:1850 与新注册顺序对齐(注释明确 sort 与顺序无关)

  • 🟡 WATCH-1: workspace_root 去固定根「动态白名单完整语义」声明部分兑现state.rs:362-365 删除固定放行成立(diff 核验),但 reload_allowed_dirs(:577)仍无条件 set.insert(workspace_root_path())(pre-existing 非 e4c2e31 引入)。即用户从 Settings KV 删除工程根后,下次 reload 会强制把工程根塞回 persistent → is_authorized 经 persistent 命中照样放行 → 「用户删白名单后需授权」的预期无法兑现。本次只删了 is_authorized 的硬编码,未触及 reload 兜底注入。非破坏性(默认行为不变向后兼容),但 commit message「动态白名单完整语义」与实现存在落差。无对应「删除白名单后需授权」的对抗测印证(test_allowed_dirs_workspace_root_authorized:741 用 default_with_root 经 persistent 命中,不覆盖删除场景)。

  • LOW-1: DEBUG_RULES 5 关键词权重(0.6-0.7)为经验值,无量化校准依据(db 分析 591ff4a0 仅统计偏好率未回归权重调优)。后续可据真实意图误判数据校准。


CR-260620-03 MED-1 修复(path 绑定 Medium 工具审批后 reload 白名单)+ LOW 清理(死 i18n key + stale 注释)(working tree 未提交:chat.rs ai_approve/ai_authorize_dir 两处 reload + i18n×2 删 confirmNewConvWhileGenerating + TopBar.vue/Sidebar.vue 注释同步·commit-policy-deferred) — 已审(PASS·1 WATCH)

  • 结论(2026-06-20·审查 agent 独立 git diff + grep/read 核验源码形态 + vue-tsc EXIT 0): PASS🔴0 🟡0 1
  • 验证: git diff HEAD 5 文件逐行核验 + npx vue-tsc --noEmit EXIT 0 + 独立 grep 全 src/ 无残留。

① MED-1 path 工具集完整性 PASS(无遗漏):

  • grep registry.register tool_registry.rs 全 AI 工具清单核验:projects.path 的 AI 工具恰为三个:
    • update_project(tool_registry.rs:481,Medium)— repo.update_field(id, field, value)(:495),白名单含 path 字段(is_allowed_column 放行)
    • create_project(tool_registry.rs:501,Medium)— 可选 path 走 bind_dir_to_project(:525)
    • bind_directory(tool_registry.rs:534,Medium)— 走 bind_dir_to_project(:544)
  • matches!(approval.tool_name.as_str(), "bind_directory" | "create_project" | "update_project")(chat.rs:484/:649)三工具全覆盖
  • 其他 Medium 工具(create_task/update_task/advance_task/create_idea/append_file/rename_file)不写 projects.path,无需 reload
  • import_project/relocate_project_path/import_projects_batchcommands/project.rs IPC(用户前端操作,非 AI 工具,不经 ai_approve),由各自 reload 路径覆盖(CR-260620-01 已审),AI 工具集无遗漏

② MED-1 reload 时机正确性 PASS(两处执行点核验):

  • ai_approve(chat.rs:479-487):reload 在 if exec_result.is_ok() 内(成功才 reload,失败不污染白名单)+ emit_data_changed 后(顺序无依赖,emit 仅 app.emit 给前端)+ matches! 限定 path 工具
  • ai_authorize_dir(chat.rs:644-652):同结构,正确 (注:此处的 matches! 永不命中,见 ⑤)
  • state.reload_allowed_dirs()(state.rs:586-640)副作用核验:读 Settings KV(:589)+ 读 repo.list_all() DB(:600)+ std::fs::canonicalize IO(:629)+ 写 allowed_dirs.write() RwLock 短临界区(:637-639)+ 保留 session 临时授权(:638-639 std::mem::take)。性能可接受(单用户桌面,项目数量级低,非热路径),无副作用风险

③ MED-1 执行分支正确性 PASS(Medium 走 ai_approve 审批非内联):

  • 三 path 工具 risk 注册仍是 RiskLevel::Medium(tool_registry.rs:483/506/536 独立 grep 印证)
  • ai_approve(chat.rs:352 消费 pending + :379 拒绝 path_auth 挂起 + :463-467 执行)是 Medium/High 审批执行入口
  • chat.rs:510 注释明确「create_idea 是 Medium risk → 经审批执行(本路径),非 process_tool_calls 内联执行」语义对齐

④ MED-1 无回归 PASS(仅 path 工具 reload):

  • matches! 精确限定三个工具名,其他 Medium 工具审批执行(create_idea/create_task/update_task/advance_task/append_file/rename_file)不触发 reload,行为不变
  • reload 在 exec_result.is_ok() 内,失败路径不 reload(避免白名单错乱)

⑤ LOW 死 key 确认 PASS(0 消费方):

  • grep confirmNewConvWhileGenerating 全 src/ No matches found — 中英两 i18n 文件定义处已删,0 消费方
  • vue-tsc --noEmit EXIT 0 印证无类型回退

⑥ LOW 注释准确 PASS(3 处同步 F-09 决策e):

  • TopBar.vue:163 /** 新建会话(父 confirmNewConversation,F-09 决策e 直接新建无中断确认) */ — 准确对齐 CR-260620-02 F-09 决策e(去弹窗直接新建,旧 conv 后台 loop 继续不中断)
  • ConversationSidebar.vue:226 * - confirm-new-conversation:新建对话(F-09 决策e 直接新建,旧后台继续不中断),交父 confirmNewConversation
  • ConversationSidebar.vue:242 /** 新建对话(F-09 决策e 直接新建,旧后台继续不中断),父调 confirmNewConversation() */

  • WATCH-1(ai_authorize_dir 第二处 reload 永不命中): chat.rs:644-652(ai_authorize_dir 内)的 matches!(approval.tool_name.as_str(), "bind_directory" | "create_project" | "update_project") reload 是冗余防御性代码,运行时永不触发。核验:ai_authorize_dir 触发条件是 process_tool_calls 预校验文件工具路径未命中白名单(chat.rs:554 docstring + :579-581 强制 approval.path_auth.is_some(),否则报错「非路径授权挂起」),而 path_auth 挂起的 pending 只会是 read_file/write_file/list_directory 等文件工具(预校验路径未命中白名单触发),绝不会是 bind_directory/create_project/update_project(这三个是 Medium 风险审批,走 ai_approve 消费 path_auth=None 的 pending,chat.rs:379 防御性拒绝 path_auth 挂起进 ai_approve)。即 ai_authorize_dir 的 approval.tool_name 永远不会命中三个 path 工具 → :649 reload 死分支。无害(防御性兜底,若未来架构调整把 path 工具也走路径授权流程则自动生效),但当前为冗余。建议保留(对称防御,防未来重构遗漏)或加注释标注「当前 ai_authorize_dir 不消费 path 工具 pending,此 reload 为对称防御兜底」。不阻塞,降 WATCH。

  • 待修项回流 todo: 🔴/🟡 项( WATCH-1 冗余防御,保留即可)

  • 待修项回流 todo: 🔴/🟡 项(🟡 WATCH-1 已修:reload_allowed_dirs 删无条件 insert workspace_root,KV 有配尊重用户 persistent,兑现"用户删白名单后工程根需授权"动态白名单完整语义)

CR-260621-01 @ + / Input Augmentation 层(df-types Augmentation + Resolver 投影 + path 脱敏 + chip 元数据 + search_files 兜底 + / 修复) — 已审(PASS·🟡13)

  • 范围:新功能 + 重构 + 安全增强。df-types 新增 augmentation 领类型(SanitizedPath newtype + MentionRef/Augmentation/MentionSpanDto/ResolveError);后端 augmentation 模块(MentionResolver async trait + ResolverRegistry + 四 impl + ProviderLocality 脱敏 + inject 段构建);skills.rs strip_frontmatter + RwLock 双检锁 + ScanResult conflicts;chat.rs 4 调用方统一 resolve_and_inject;audit/mod.rs search_files 兜底改 Denied;前端 ChatInput chip span 记录 + MessageList 元数据驱动切段(弃正则)+ spans 全链路透传。
  • 维度:协议(serde tag/rename/transparent/newtype 向后兼容)/ Resolver(async trait + 单条失败不阻断)/ 安全(path 脱敏 Local 全路径 Remote basename)/ spans 透传链路完整性(8 环节逐环节核验)/ chat.rs 注入收敛一致性 / search_files 兜底回归(其他文件工具不变)/ 前端 chip 元数据驱动(非正则)
  • 核验方式:独立 grep/read 逐文件核验源码当前形态(11 新文件 + 8 改文件),不信任何声明。

复审结论(2026-06-21·独立 grep/read 核验源码当前形态,不信声明/文档/会话描述): PASS🔴0 🟡1 3

① Augmentation 协议 PASS(serde 严格对齐 + 向后兼容):

  • MentionRef #[serde(tag="kind", rename_all="snake_case")] + 字段 #[serde(rename="id"/"name")],变体 project/task/idea/skill,round-trip 单测印证(augmentation.rs:350-401)
  • Augmentation 同构 tag="kind" + path: Option<SanitizedPath>#[serde(default, skip_serializing_if="Option::is_none")](:161),无 path 时 JSON 不含 path 字段(单测 :439-453 印证)
  • SanitizedPath #[serde(transparent)](:30)— wire 上是裸字符串无 {inner} 包裹,前端 type SanitizedPath = string(types.ts:413)对齐
  • MentionSpanDto.ref_id #[serde(rename="refId")](:246)— camelCase 对齐前端 TS refId(types.ts:402)
  • 向后兼容:serde tag=kind + 枚举,老前端遇未知 kind 反序列化失败抛 Err(非"忽略"),但仅在新 mention 类型新增时才有此风险——当前 4 变体与前端 TS union 严格对齐(types.ts:400 kind: 'project'|'task'|'idea'|'skill')无未知 kind 场景。Augmentation TS 类型宽松(types.ts:370 不强制校验变体),前端宽容。设计合理

② Resolver trait + 四 impl + resolve_all 单条失败不阻断 PASS:

  • MentionResolver async_trait(mod.rs:34-48)+ 四 impl(resolvers.rs:56/121/184/246)kind 分别返 "project"/"task"/"idea"/"skill"
  • resolve_all for 循环逐条 resolve,Ok push augs / Err push errors(不 break 不 return,registry.rs:56-72);kind 无对应 resolver 收集 KindMismatch 不 panic(单测 :107-122 印证)
  • ProjectResolver 经 ProjectRepo::get_by_id + path 经 sanitize_for 包 SanitizedPath(resolvers.rs:76-104);TaskResolver join ProjectRepo::get_by_id(task.project_id) 取 project_name(:156-162);IdeaResolver 无 project_id 无 path;SkillResolver 调 read_skill_content_stripped 取剥 frontmatter 正文(:267)
  • 循环依赖规避:Resolver 持 Arc<Database>Arc<AppState>(resolvers.rs:9-11 注释明确),AppState 持 Arc<ResolverRegistry>(state.rs:273),无环

③ path 脱敏 PASS(locality 判定 + sanitize_for):

  • locality_of(provider)base_url.to_lowercase(),LOCAL_MARKERS 含 localhost/127.0.0.1/0.0.0.0/::1/ollama,任意子串命中即 Local(sanitize.rs:30-44)
  • 单测覆盖 localhost/127/0.0.0.0/[::1]/ollama/LOCALHOST(大小写不敏感)/api.openai.com/空串(:98-144),边界完整
  • sanitize_for(raw, Local) 原样返 / RemotePath::file_name basename,失败回退 [remote-path-hidden](:54-64),防裸泄盘符/用户名/公司目录 + 防 LLM 拿乱码路径瞎调文件工具
  • 实际生效路径核验:ProjectResolver resolve 内 sanitize_for(raw, loc)(resolvers.rs:96)→ SanitizedPath::new 包 newtype,loc 由 chat.rs:109 sanitize::locality_of(provider) 传入,provider 来自 get_active_provider 真实 DB 记录。闭环

④ 【重点】spans 透传链路完整性 PASS(8 环节逐环节核验,无断链):

环节 file:line 核验
1 ChatInput.selectMention 记 spans ChatInput.vue:432-438 push {start: before.length, length: label.length, kind: item.type, refId: item.id, label}
2 ChatInput.handleSend 快照 + trim 偏移修正 ChatInput.vue:567-571 rawLeadingWs = inputText.length - inputText.replace(/^\s+/).length,span.start 整体左移 rawLeadingWs(剥前导空白致偏移失效修复)
3 ChatInput.handleSend 透传 store.sendMessage ChatInput.vue:608 store.sendMessage(text, skill?.name, false, parts, snapshotSpans.length > 0 ? snapshotSpans : undefined)
4 useAiSend.sendMessage 签名 + L0/L2 透传 useAiSend.ts:291,296,326 sendMessage(text, skill, forceMode, parts, spans),L0 doSend(text, skill, false, parts, spans) / L2 force doSend(text, skill, true, parts, spans)
5 useAiSend.doSend 调 aiApi useAiSend.ts:107,109 force: aiApi.forceSend(text, lang, skill, override, parts, convId, spans) / normal: aiApi.sendMessage(..., spans)
6 aiApi.sendMessage/forceSend invoke mentionSpans ai.ts:16-27,31-42 invoke('ai_chat_send', {..., mentionSpans: mentionSpans && mentionSpans.length > 0 ? mentionSpans : null}),空数组不传(后端默认 None)
7 IPC ai_chat_send mention_spans 参数 chat.rs:303 mention_spans: Option<Vec<MentionSpanDto>>(Tauri 自动 camelCase→snake_case 转换)
8 chat.rs resolve_and_inject 转 MentionRef chat.rs:88-112,119-138 spans 经 span_to_mention_ref 按 kind 转 MentionRef,合并 skill,resolve_all 投影成 Augmentation,build_augmentation_segment 拼段
  • 【用户重点关注的断链点核验】stores/ai.ts 是否透传 spans:stores/ai.ts 本身不实现 sendMessage(它通过 ...useAiSend() 展开到 useAiStore,stores/ai.ts:226),真正的 sendMessage 在 useAiSend.ts:291 且已正确透传 spans(第 4 环节已核验)。无断链
  • L1 入队续发不挂 spans(useAiSend.ts:316-321 queue.push 无 spans 字段):设计取舍非 bug——队列项为韧性保内容,mention 区间在首次发送时已与文本对齐,排队等待后用户可能改输入,续发用旧 spans 语义模糊;注释 :287-289 明确标注,与现有 parts 入队续发的设计一致

⑤ chat.rs 注入收敛一致性 PASS(4 调用方统一):

  • ai_chat_send(:382)/ ai_chat_force_send(:1380)/ ai_regenerate(:229)/ ai_chat_edit(:1224)四调用方均经 resolve_and_inject(&state, &provider_config, &skill/&None, &mention_spans/&None, &lang)
  • regenerate/edit 传 &None, &None(无新注入诉求,注释 :227-228/:1223 说明),空结果返 "" 跳过拼接
  • 旧 skill 注入块替换核验:grep read_skill_content|skill_content|注入技能 全 chat.rs 无残留旧式注入代码,仅 resolve_and_inject docstring 提及"取代旧实现直接 read_skill_content 灌全文含 frontmatter"(:78-79 注释)。skill 注入已统一走 SkillResolver → read_skill_content_stripped 剥 frontmatter

⑥ skills.rs PASS(strip_frontmatter 状态机 + RwLock 双检锁 + ScanResult):

  • strip_frontmatter(skills.rs:62-93)状态机:首行非 --- 返原文(:69-77)/ 首 --- 进 in_fm 找下个 --- 退出取正文(:79-89)/ 未闭合 frontmatter 返空串容错(:91)。单测覆盖 normal/no_fm/empty/only_fm/crlf(:348-376)
  • RwLock<Option<Vec<SkillInfo>>> 双检锁(skills.rs:278,290-311):快路径读锁命中返 guard / 慢路径释放锁扫盘 → 写锁填回(二次检查防并发重复扫)→ 再取读锁。skills_cached() clone 返 owned Vec(:317-320),invalidate_skills() 写锁置 None(:326-329)支持 ai_reload_skills 热重载(config.rs:41-44 invalidate + skills_cached)
  • ScanResult{skills, conflicts}(skills.rs:41-44):按 name 去重(skills>commands>plugins 优先级保留首份)+ 同名 >1 收集 conflicts(:250-266)+ SkillInfo.duplicates 回填(:32-33 serde skip_serializing_if Option::is_none 向后兼容)
  • read_skill_content_stripped(skills.rs:335-341):缓存命中读文件 → strip_frontmatter 剥正文。缓存未命中/文件读失败返 None

⑦ search_files 兜底 PASS(Denied 不弹窗,其他工具不变):

  • check_file_tool_auth(audit/mod.rs:175-214):paths 任一命中黑名单 → Denied(:191 短路)/ 全授权 → Authorized(:201)/ 有 pending_dirs(未命中白名单非黑名单)→ tool_name == "search_files" 分支返 Denied 提示(:202-210)/ 其他 → NeedsAuth 弹窗(:212)
  • 【用户重点关注的回归核验】其他文件工具行为不变:extract_file_tool_paths(:143-161)单路径工具列表 read_file | write_file | list_directory | patch_file | file_info | append_file | delete_file | search_files(:155-156)+ rename_file 双路径(:145-154)。check_file_tool_auth 内只有 tool_name == "search_files" 单一特判(:202),read/write/list_directory/patch_file/file_info/append_file/delete_file/rename_file 八个工具在 NeedsAuth 场景仍走原弹窗逻辑完全不变
  • 黑名单短路在 search_files 特判之前(:191 for 循环内),故 search_files 命中黑名单也走 Denied(reason 来自 check_path_authorization 的黑名单原因,非 search_files 特定提示),逻辑一致
  • handler 注册核验:tool_registry.rs:1574 "search_files" 注册为真实工具(非空壳),兜底改 Denied 不影响 handler 存在性(LLM 仍可调用,只是未授权目录时返 Err 提示而非弹窗)

⑧ 前端 chip 元数据驱动 PASS(弃正则):

  • ChatInput.selectMention 记 span(ChatInput.vue:432-438)+ watch skill/@ 共存(:254-268 删原"pendingSkill 非空强制关 mention"分支,skill chip 与 @ 输入态正交)
  • MessageList.segmentUserContent(MessageList.vue:190-235)纯元数据驱动非正则:按 spans start 升序排序 → 逐 span 安全校验 content.slice(start, end) === span.label(:213,不等则降级跳过)→ 前导 text + chip 段 + tail text。越界(:209)/重叠(:211)/内容不符(:213)三路降级。极端全降级补完整 text(:230-232)
  • 模板渲染(MessageList.vue:927-933)v-for segmentUserContent 按 seg.type 渲染 text/chip,chip class 按 seg.span.kind 区分
  • trim 偏移修正正确性(ChatInput.vue:567-571):span.start 原相对 inputText(含前导空白),text = inputText.trim() 剥前导空白后偏移失效;rawLeadingWs 计算前导空白长度,span.start 整体左移。尾部 trim 不影响(chip 不在尾部空白区域)。修复正确,否则所有 chip start 错位致渲染降级纯文本

🟡 MED-1:prompt.rs:108-111 docstring stale(双套注入机制未标注)

file:line 问题 建议
src-tauri/src/commands/ai/prompt.rs:108-111 build_system_prompt docstring 仍宣称「使 LLM 能解析用户消息中的 [项目: xxx] / [任务: xxx] 标记并对齐到真实实体(名称+状态+描述)」。但本批改动已把"@ mention 对齐到真实实体"的工作转交 augmentation resolver(chat.rs resolve_and_inject → build_augmentation_segment 独立段注入)。当前 build_system_prompt 仍全量注入最近 20 项目 + 20 任务到 system prompt(:118-136 全局清单),这是两套并行的机制:全局清单(向 LLM 提供可选项目/任务池)+ augmentation 精准投影(用户 @ 的特定实体详情)。docstring 把两套混为一谈,且"对齐到真实实体"的说法现已不准确(对齐工作已下沉到 resolver)。 修订 docstring 区分两套机制:build_system_prompt 注入"全局项目/任务清单(供 LLM 知晓可选范围)";augmentation 段(独立注入,见 chat.rs resolve_and_inject)负责"用户 @ 的特定实体详情对齐"。或者评估全局清单是否仍必要(若 augmentation 已覆盖用户所有 @ 场景,全局清单可能是冗余 context 膨胀——但这属行为评估,本次只标 docstring stale)。降 MED:纯文档不准,不影响行为;但易误导后续维护者以为标记解析仍在 build_system_prompt。

LOW-1:Augmentation augmentations 字段当前无消费方(预留)

src/api/types.ts:370 AiMessage 加 augmentations?: Augmentation[] 字段,注释 :364-369 标注"主要用于落库消息回显/调试,前端宽容对待"。当前前端无任何代码读 msg.augmentations(grep 全 src/ 仅类型定义处 + 注释)。属预留字段(后端 resolve 后的 Augmentation 未来可能回传落库,前端先占位)。零调用方预留保留(对齐 memory dead-code-reserve-keep 原则),不删。

LOW-2:ChatInput MentionItem.type 仅 'project'|'task'|'idea' 无 skill(/ 走 selectSkill 不走 selectMention)

ChatInput.vue:325 MentionItem.type: 'project' | 'task' | 'idea' 无 skill 变体。设计正确:/ 技能走 selectSkill(:303)独立 chip 态(不插 inputText,清空输入),不走 @ selectMention。故 pendingMentionSpans 永不含 skill kind。但 chat.rs span_to_mention_ref(chat.rs:133-135)处理了 skill kind——这是防御性兼容(若未来 @ 技能也走 mention,或历史消息 spans 含 skill kind 时后端能正确 resolve)。无 bug,设计合理。

LOW-3:augmentations/en 字段名混用(Augmentation 内 snake_case,MentionSpan refId camelCase)

types.ts:383-384 注释明确:"Augmentation 内部字段沿用后端 snake_case(path/project_name 等,与 wire 严格对齐);refId 用 camelCase"。两种命名风格混在同一层。设计取舍:Augmentation 是后端投影镜像(保 snake_case 与 wire 对齐减少转译层),MentionSpan refId 是前端约定(camelCase 对齐 TS 风格)。合理但风格不统一,LOW 标注。


  • 🟡 MED-1: prompt.rs:108-111 docstring stale(双套注入机制未标注,易误导维护者)
  • LOW-1: AiMessage.augmentations 预留字段无消费方(保留符合预留原则)
  • LOW-2: ChatInput MentionItem.type 无 skill(/ 走 selectSkill 非selectMention,设计正确)
  • LOW-3: Augmentation snake_case 与 MentionSpan refId camelCase 命名风格混用(设计取舍)
  • 待修项回流 todo: 🔴/🟡 项(🟡 MED-1 纯 docstring stale,建议顺手修订不阻塞)

已审归档

已审 CR 段迁独立文件: 待审查归档/2026-06.md