# 待审查队列

> **职责分离**:
> - **主会话**(编码合并后 / 巡检)→ 【**写入**】"当前队列"区,登记待审查项(范围 + 维度 + 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-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 段迁独立文件: [待审查归档/2026-06.md](./07-项目管理/待审查归档/2026-06.md)
