# 待审查队列

> **职责分离**:
> - **主会话**(编码合并后 / 巡检)→ 【**写入**】"当前队列"区,登记待审查项(范围 + 维度 + commit)。
> - **审查 agent**(独立会话 / workflow)→ 【**读取**】"当前队列",执行审查,完成回填结论 + 移到"已审归档"。
>
> 这里**只放审查登记**,不放推进待办(推进待办见 [todo.md](./todo.md))。
> 主会话**不在这里做审查**,只登记 + 读 todo 推进。审查发现的【待修代码项】回流 todo 作推进待办。

## 审查规则(审查 agent 遵守)

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

## 状态图例

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

---

## 当前队列

### CR-260624-01 AI 路径授权三档化(once/session/always) — ✅ 已审(PASS·单 agent + 6-agent 对抗深审 0 finding)

- **范围**:AllowedDirs 加 `once` 层(state.rs:444)+ ai_authorize_dir decision 改 match 四分支 once/session/always(chat.rs:846)+ 执行后 clear_once(chat.rs:924)+ 前端 DirAuthDialog 加"当前会话"按钮 + useAiApproval.ts/api/ai.ts/api/types.ts decision 联合类型扩 'session' + i18n zh/en 加 dirAuthSession。
- **维度**:
  - (a) **once 清空时机**:clear_once 在 execute 后无条件清(state.rs:924 附近 chat.rs)。竞态:同 once 路径并发工具调用?现状工具串行(per_conv 单 generating),无并发,OK——但核验确认。
  - (b) **session vs once 语义边界**:session 切会话清(conversation.rs clear_session)/ once 执行后清 / reload_allowed_dirs 保留 both(state.rs:810 take once + session)。核 reload 是否漏保留 once。
  - (c) **is_authorized 读 once**:state.rs is_authorized 三层 `persistent || session || once` 任一命中。核 once 命中分支存在。
  - (d) **前端全链路**:DirAuthDialog handleAuthorize / useAiApproval approveToolCall / api.authorizeDir 三处 decision 联合类型均含 'session',types.ts decision: string 不受限(无类型保护,但 IPC 接受任意字符串,match _ 兜底)。
- **核验方式**:grep `once|session` 贯穿 state.rs/chat.rs/前端 + 核 clear_once 调用点 + deny 分支(早 return,once 未写,不需清,核确认)。
- **关注点**:批量 approve 仍用 once(单卡语义,不批量 always,useAiApproval:110);always 降级写 session(原降级 add_session,语义对齐)。
- **审查结果**(2026-06-25):单 agent 审查(a2b2b35,6 维度全过)+ workflow 对抗深审(w9waeyg36,6 agent 并行 review + 每条 finding 独立 verify,**0 confirmed finding**)。once 清空时机(串行无并发误清)/ session-once 边界 / is_authorized 三层读 / deny 不触发 clear_once / 前端类型一致 全过。

### 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 load` → `run_agentic_loop max_iterations=0` → `effective_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·🟡1⚪2)

- **范围**:三方审查(安全/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-1804` 改 `entry.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_verbatim` 用 `s.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_dirs` 读 `repo.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🟡4⚪4) → 🔴已修(主代闭环 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_idea` 中 `if 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:410` 删 `pub risk_level: RiskLevel` 字段 + `:45` 删 `use 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.rs` 删 `emit_human_approval_request`(零调用·实际走 `send()`)+ SendError import · `df-ai/ai_tools.rs` 删 `ToolExecutionResult`(零消费者)。
  - **批2 前端**:`useMarkdown.ts` 删 `clearHistoryCache`(零消费) · `useConfirm.ts` 删 `ConfirmFn`/`AnswerFn`(零 import) · `useAiPanel.ts` `restoreUiState` 去 `export`(零外部调用) · `markdown.ts` stripMd 围栏正则 `[a-zA-Z]*`→`[^\n]*`(吃任意 infostring 如 rust,norun) · `time.ts` `formatDate` 读 `i18n.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.locale` 是 `WritableComputedRef` 非 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.loading`→`v-else-if store.error`→`v-else-if filteredIdeas.length===0`→`v-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.rs` 删 `tool_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<TaskRecord>;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:13` 均 `use 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) use` 在 `mod` 声明(: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<Semaphore>>> · `: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<Semaphore>>>> · `:125` per_conv_permits:Arc<AtomicUsize>(热改) · `: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<Semaphore>>>>(限流槽) · 两者独立 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<String> = session.per_conv.iter().filter(|(_,c)| c.generating).map(|(id,_)| id.clone()).collect()` — 遍历全 per_conv(HMR/dev 热载多 conv 并发跑场景全覆盖) | ✅ |
| 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<String> 真实存在);`: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 listener` → `lib.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·🟡1⚪2)

- **结论(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_trash` 用 `unwrap_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·🟡1⚪1)

- **结论(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 `isGenerating` 调 `state.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-title` 含 `min-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 `:14` 加 `AiMessageRepo`,完全对齐 `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 看不到任务工具,会回「无法完成」或只用消息上下文答)。建议:`Debug` 加 `ToolDomain::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.0` 时 `conf >= 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·🟡1⚪1)

- **结论(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"](:279)+ 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_batch` 是 `commands/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·🟡1⚪3)

- **范围**:新功能 + 重构 + 安全增强。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)` 原样返 / `Remote` 取 `Path::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](./07-项目管理/待审查归档/2026-06.md)
