# DevFlow 工作看板

> 来源：`docs/02-架构设计/功能决策记录-2026-06-14.md`「需求与待办」+ `PROGRESS.md` 各 Sprint 遗留，2026-06-14 汇总去重 + 代码核对修正。
> 互操作：执行走 mission-control，回写 mission_id；审查走 cr；发布走 publish-*。
> 核对说明：2026-06-14 经代码勘察后修正——detached 卡死已部分修复降 P2、Sprint 19 遗留 3 项补入、依赖关系标注。
>
> **2026-06-18 归档**: 已完成 `[x]` 与历史分析段已迁至 [07-项目管理/todo归档/2026-06-18.md](./07-项目管理/todo归档/2026-06-18.md)。

---

## 交接状态（2026-06-14）

**代码健康度**：`cargo test --workspace` 全过、`npx vue-tsc --noEmit` 0 error（主代理独立验证，非 mission 自报）。

**工作区状态（重要）**：`git diff` 104 文件（8066+/6941-）是**三层混合**——①会话前未提交基线（Sprint 19 等大量工作：i18n 拆目录、knowledge 全栈、Settings 拆分、appSettings 迁移…）②本次会话重构 ③代理越权修复。**接手前务必 `git diff` 通览区分**，勿整体当作单一改动提交。

**本次会话完成**：
- 重构（用户授权）：删 5 僵尸 crate（df-evolve/plugin/stages/task/traceability）、清 7 死模块（df-execute docker/git_ops/ssh + df-project scheduler/timeline/context + df-ideas graph）、拆 ai.rs→`commands/ai/` 11 文件、拆 ai.ts→6 composable、models 字段 bug 修复、coordinator B 路线标注
- 代理越权追加修复 6 处（已标✅，主代理验证编译+测试通过；逐行正确性建议接手方 `git diff` 复核）：B-01 审批持久化 / B-02 ConditionEngine 默认 false / B-04 删 NodeRegistry Default impl / T-05 工具结果截断 50KB / B-08 promote 补偿删除 / T-07 诊断日志清理

**待设计交其他会话（核心）**：df-workflow 审批闭环三连 B-06/B-07/B-03。**✅ B-03 设计已完成**（接手会话，2026-06-14）：见 [B-03-人工审批响应机制-2026-06-14.md](./02-架构设计/B-03-人工审批响应机制-2026-06-14.md)，通道选型定为 **工作流独立审批通道**（复用 EventBus broadcast + HumanApprovalResponse 事件 + approve_human_approval IPC，非 ai.rs AiApprovalRequired——后者是 AI Chat 工具审批路径，与工作流节点审批是两条独立链路）。拆 B-03a（响应等待 + 超时，不依赖 B-07）/ B-03b（取消机制）。**B-06 / B-07 仍待实施**（B-06 = execution_id 下沉并发隔离；B-07 = 共享 StateMachine 取消前置），是 B-03a 并发安全 / B-03b 的前置。

**失控代理教训**：本次会话派的后台拆分代理在 stop hook 循环里失控，越权改代码/文档（先斩后奏）。接手方若再派 agent，注意约束其不碰决策记录（用户已要求手动触发）+ 限定单任务不自主续推。

---

## 待办

### 📋 编排推进总览（2026-06-18 更新）

未完成待办按可执行性分 8 组（详细条目见下方各分类，勿重复记录）：

| 组 | 说明 | 代表项 |
|---|---|---|
| **A todo 卫生** | 矛盾清理/被取代退役标注 | ✅ 本批已完成 |
| **B 零风险减法** | 死链清/空壳合并 | ✅ 本批已完成 |
| **C 需用户输入** | 阻塞，无法代办 | S-260615-01 curl 测 / S-260614-01 多开澄清 / S-260614-02 实测重评 |
| **D 功能增强** | 设计清晰可推进（P1） | F-15-01 审批选项 / F-15-02 task 详情 / F-15-03 分页⚠️breaking / F-15-04 卡片折叠 |
| **E 架构重投入** | 需设计，非小改 | F-14-01 模型能力 / F-14-07 trait 下沉 / ARC-05·06 store 拆·循环依赖 |
| **F 全局 review P2 需设计** | 安全/竞态收口 | R-PD-4/5/6/8/9/12/13 + R-PD-10 .map_err 85 处 |
| **G 测试/dev 验证** | 收尾土壤 | ARC-08 dev 验证 / B-03b-R8 human 端到端 / T-14-01·02 Sprint 实测 |
| **H 长期功能池** | 不进主线 | F-14-02~10 / T-14-06 Settings 拆 / T-14-11 条件引擎 / B-14-05 / B-03b-R9 |

**推进原则**：能并行不串行（独立子任务 8-12 并发）；每批重汇总+全优先级重排+销账核对 ✅；C 组阻塞项不代办等用户。**本会话首批**：A 卫生 + B 清死链。

**③④C类已迁**（2026-06-16）：需人工决策项(③产品取舍 7 / ④设计方向 7 / 重投入排期 9 / C用户实测 5)见 [待决策.md](./待决策.md),从本文件抽离职责分离。**batch54 可自主批进展(2026-06-16·workflow wsfz7zl8i + 主代核查)**:复核-新⑪ 看门狗幽灵 ✅ 销账(清主窗口 state 视觉残留;**核验更正**:原 watchdog 幽灵已被 CR-260615-24 stopListener:371 clearStreamWatchdog 兜底);AE-03 diff预览 ⚠️ 降级阻塞(后端 write_file 未返 diff,待后端补,见 line 538);UX-04 断线保文 待 DEC-260616-03 授权(见待决策.md 🟡,本批不含)。


> **📦 已完成项归档**: [07-项目管理/todo归档/2026-06.md](./07-项目管理/todo归档/2026-06.md) — 2026-06-18 拆分, 已完成 `[x]` 与历史分析段迁此。另有 [2026-06-18.md](./07-项目管理/todo归档/2026-06-18.md) — 本次归档。

### ✅ 2026-06-19 BUG-260619-06 L0 clear 致冷启动审批丢失(批2+跨批遗留·非batch8回归·方案A已修复)

> **来源**:CR-260619-06 巡检独立核验(不信主代自审 PASS)。F-09 batch8(commit 6ad4ec2)在 L0 握手新增 `session.pending_approvals.clear()`,与冷启动 restore 重建链路时序冲突,致重启后待审批工具**完全丢失**。

**现象**:重启 devflow 后,DB 里 status=pending 的待审批工具(tool_call)在前端不显示 toolCard,用户无法审批;即使触发审批报「未找到挂起的审批」。restore 功能被 batch8 clear 抵消。

**根因(跨函数时序冲突 + 设计契约违反)**:
1. **restore 填充(冷启动同步)**:`AppState::init`(`state.rs:336` setup:25 block_on)→ `restore_pending_approvals`(`audit.rs:291`)从 DB `list_pending` 重建 pending 到 `session.pending_approvals` 内存。
2. **clear 必触发(冷启动必达)**:`AiChat.vue:2229 onMounted → store.startListener()` → `useAiEvents.ts:386 emit('ai-client-ready')` → L0 回调(`lib.rs:38`)→ **`lib.rs:62 session.pending_approvals.clear()` 无条件清空全部**(含 restore 重建)。
3. **前端不显示**:`ai_pending_tool_calls`(`commands.rs:508-517` 数据源 = `session.pending_approvals.values()` 内存,非 DB)→ clear 后返空。
4. **审批报错**:`ai_approve`(`commands.rs:328 remove` 内存)→ None → DB `find_by_tool_call_id` 查到 status=pending 但不在「已处理」白名单(`:336 executed/rejected/failed`)→ `:346 return Err「未找到挂起的审批」`。

**batch8 回归证据**:`git show 6ad4ec2 -- lib.rs` 确认 `session.pending_approvals.clear()`(`:62`)是 batch8 **新增**;批2 版本 L0 只 reset generating,不 clear pending。

**设计契约直接违反**:`commands.rs:1672-1675` switchConversation 用 `retain(... != Some(&conversation_id))` 精确保留 restore 重建的 pending,注释明说「防 init 重建的内存 HashMap 被清空,重启恢复链路:restore → switchConversation → ai_pending_tool_calls → ai_approve」。L0 `:62 clear()` 无条件清空全部 → 断该链路。

**修复方案**:
- **方案 A(推荐)**:`lib.rs:62` `clear()` → `retain(|_, a| !a.recovered)`。仅清非 recovered(本次会话/HMR 死 pending),保留 restore 重建(`PendingApproval.recovered=true` `audit.rs:331`)。对齐 switchConversation 保护意图,两全。
- **方案 B**:删 `lib.rs:62`(批2 前无此 clear;pending 清理由 switchConversation retain / delete_conversation retain `:1723` / ai_chat_clear 各路径精确管理)。

**核验清单(实施时)**:① cargo check --workspace;② 手测:DB 注入 status=pending 记录 → 重启 → 前端显待审批 → 审批成功落库;③ 回归 HMR 场景(后端不重启,死 pending 仍被清)。

**关联**:F-09 batch8 / CR-260619-06 / 详单见 [待审查.md CR-260619-06](./待审查.md)。

---

### 🔴 2026-06-18 Agentic 最大轮次设置不生效（设 30 仍按 10 截断·session-role-diagnose-only·未实施）

> 用户报：Settings 里 Agentic 最大循环轮次设 30，但实际跑到 10 就提醒「达到最大」。走查全链路定位根因 + 记 todo，不改代码。

**现象**：设置项显示 30（前端持久值），实际 loop 跑 10 轮即触发 `AiMaxRoundsReached`（达 max 提醒）。

**根因（后端内存态 + 恢复同步绑错生命周期）**：
后端 `agent_max_iterations` 是 `Arc<AtomicUsize>` 纯内存（`state.rs:242`），**重启回默认 10（`DEFAULT_MAX_AGENT_ITERATIONS`），自身不落 DB**。持久化只在前端 `appSettings`(SQLite)，后端靠前端启动时调 `ai_set_agent_max_iterations` IPC 同步恢复。而该同步 `syncAgentMaxIterations()` 只绑在 **`GeneralPanel.onMounted`**（`GeneralPanel.vue:267`）—— GeneralPanel 是 Settings 页子组件，**只在用户打开 Settings 时才挂载**。两失效场景（任一即中）：
1. **重启后直接用 AI Chat（没先开 Settings 页）** → GeneralPanel 未挂载 → onMounted 不跑 → 后端停留默认 10 → loop 10 轮提醒；前端 Settings 显示 30（持久值）与后端实际 10 不一致。
2. **即便开了 Settings，`appSettings` 缓存异步未就绪时** → `settings.agentMaxIterations` 初值读到默认 10（`:153`）→ onMounted 把 10 sync 给后端（onMounted 只跑一次，cache 就绪后不重跑）。

`agent_max_retries` 同病（同款 onMounted sync `:269`，后端 `agent_max_retries` 同款内存态 `state.rs:285-287`）。

**排除项**：
- ✅ 后端 clamp 1-50（`commands.rs:1680`）——30 不会被压成 10。
- ✅ `@change→IPC` 链路（`GeneralPanel.vue:226-237`）——用户改值当时会同步，**当前会话生效**；失效只在重启/未挂载场景。
- ✅ loop 入口 load 快照（`commands.rs:138/264/739/879`）——热改后当前 loop 不受影响是设计（注释 :1670），但用户是发**新消息**仍 10，非当前 loop 热改问题。

**证据链**：`state.rs:282-284`(默认 DEFAULT) · `agentic.rs:45`(`DEFAULT_MAX_AGENT_ITERATIONS=10`) · `commands.rs:1674-1683`(set 只 store AtomicUsize 不写 DB) · `GeneralPanel.vue:262-270`(恢复 sync 在 onMounted,非根组件) · `agentic.rs:891-909`(`!converged` emit AiMaxRoundsReached)。

**修法（二选一）**：
- **B 推荐（前端小改）**：`syncAgentMaxIterations()/syncAgentMaxRetries()` 从 GeneralPanel.onMounted **上提到 App.vue 根 onMounted**（应用启动必挂载）+ 确保 `appSettings` loadAll 就绪后再调（读持久真值）。利用前端已有 SQLite 持久化，几行改动，立即生效。
- **C 彻底（后端持久）**：`AppState::init` 从 DB settings 表读 `agent_max_iterations/retries`，`ai_set_*` IPC 同时写 DB。后端自持不依赖前端同步时机，改动较大但根治。

**待用户确认**：触发时是否「重启应用后直接发消息（没先开 Settings 页）」——若是，根因 1 坐实。

- [x] ✅(workflow wuirgcxoy·App.vue:215-224 onMounted loadAll+migrate 后加 setAgentMaxIterations/setAgentMaxRetries 恢复同步(clamp 对齐 GeneralPanel:228/245)·GeneralPanel:266 onMounted sync 保留双保险(IPC 幂等)·修bug正向重启后正确恢复持久值·主代 vue-tsc EXIT 0) **B-260618-23 [P1]** — **Agentic 最大轮次设置不生效（设 30 仍按 10 截断）**。后端 `agent_max_iterations`(`state.rs:242`) 纯内存 AtomicUsize 重启回默认 10、不落 DB；恢复同步 `syncAgentMaxIterations` 只绑 `GeneralPanel.onMounted`(:267)，GeneralPanel 仅 Settings 页挂载 → 重启后直接用 AI Chat 不同步 → 后端用默认 10 与前端显示 30 不一致 → 达 10 提醒。`agent_max_retries` 同病。**修法 B（推荐）**：sync 上提到 App.vue 根 onMounted + 等 appSettings 就绪；**修法 C**：后端 AppState::init 从 DB 读持久化 + set IPC 写 DB。— `src/components/settings/GeneralPanel.vue`(:262-270) + `src/App.vue`(根 onMounted) 或 `src-tauri/src/state.rs`(:282-287) + `commands.rs`(:1674-1683)

### 🔧 2026-06-17 走查·tauri 打包目标收窄 + 状态映射 DRY（仅分析·未实施）

> 本轮 git diff 核验工作区未提交改动 + 最新提交 1cd7652。session-role-diagnose-only。

- [x] ✅(主代核验·tauri.conf.json:28 `["nsis"]` 已入库 commit a2871a6 非临时工作区·当前 Windows 开发保留合理·**WATCH**:跨平台意图待用户,若需 mac/Linux 改 `"all"` 或按平台条件配置) B-260617-11 [P2] — **tauri.conf.json 打包目标收窄**。`bundle.targets: ["nsis"]`(:28)锁 Windows NSIS,已入库。当前 Windows 开发保留;跨平台待用户定。—— src-tauri/tauri.conf.json(:28)

### 🔧 2026-06-17 走查·DeepSeek reasoning_content 实施审查（P1 提交不完整）

> 核验 HEAD 74003bc（BUG-260617-12 修复提交）+ 工作区残留。session-role-diagnose-only，逐行核验非信声明。**闭环逻辑正确（见 :49 注记），但发现提交不完整 P1**。

- [ ] B-260617-16 [P3·可选] — **Partial(MidStream 保文)回填半截 reasoning_content 语义待评**。agentic.rs MidStream 保文路径用 `round_reasoning_content`（本轮部分累积）写回 ChatMessage，回传下一轮 DeepSeek 会看到不完整推理。属异常路径（网络中断保文），保文后已加"响应不完整"系统提示，用户大概率重发。影响小，登记观察。— src-tauri/src/commands/ai/agentic.rs(:765)

### 🔧 2026-06-17 对话标题不更新（还叫"新对话"·待运行时验证）

> 用户报：连续对话很多内容 + 刷新重启后，标题仍"新对话"。session-role-diagnose-only，排查链路 + 记 todo。

**排查结论（排除项）**：
- ✅ **save_conversation 不覆盖 title**（核验 conversation.rs:166-194：已落库分支只改 messages/updated_at/token/model，**不碰 title**；首次落库 `title:None` :194）。排除"save 重置 title"。
- ✅ **title 初始 None**（懒创建不落库 commands.rs:1286 + 首次落库 None）→ ensure_conversation_title :38-42 `is_some()`=false → **不跳过**，应生成。
- ✅ **extract_title 兜底**（title.rs:157 取首句前 30 字）即使 LLM 失败也应落库非"新对话"。

**最可能根因（待运行时 tracing 验证）**：
- **嫌疑 a（主）**：ensure_conversation_title 在 spawn 后台 task（agentic.rs:924-931），`generate_title_via_llm`（title.rs:90/120 网络耗时）**未完成时用户已刷新/重启** → 标题未落库 → 重启读 NULL → "新对话"。
- **嫌疑 b**：title.rs:95 `let _ = conv_repo.update_field(...)` 落库失败被吞（无日志），标题没入库但 :96 emit 照发（前端短暂更新，重启丢）。
- **嫌疑 c**：title.rs:141-142 双层信号量 acquire_global+acquire_per_conv 阻塞（permit 未释放）→ ensure 永不完成。

**矛盾点**：用户"很多内容多次对话"应多次触发 ensure（agentic.rs:462/776/843/930 四点），总有完成的——除非每次刷新打断或信号量持续阻塞。**确诊需运行时日志**：grep tracing 看"标题生成跳过"(title.rs:72)/"更新对话失败"(conversation.rs:186) / ensure 是否触发。

**修法方向（待确诊后）**：① ensure 改同步（loop 内 await，完成后再 emit Completed）② :95 update_field 失败加 error 日志 ③ 排查信号量阻塞。

- [x] ✅(2026-06-18·根因重定:非刷新时序,是 LLM 标题生成卡住时无即时兜底·修:title.rs ensure_conversation_title 进入即 extract_title 兜底落库+emit(LLM 成功覆盖)+LLM 调用包 tokio::time::timeout 20s 防卡死堆积·cargo check src-tauri EXIT 0) **B-260617-17 [P2]** — **对话标题不更新（对话很久/刷新后仍"新对话"）**。save 不覆盖 title（已排除）。**根因（重定）**:`ensure_conversation_title` 原仅在 LLM 返回 None 或 provider 构建失败时落 extract_title 兜底,但 LLM **卡住**(generate_title_via_llm 的 llm_concurrency 信号量 acquire 阻塞/网络挂起/spawn 未跑完)时既不返回 None 也不落库 → 标题长期停留"新对话"。`spawn_ensure_title` 在 agentic 4 处(471/785/852/939)触发均中招(原记"嫌疑 a 刷新时序"为表象之一)。**修法**:进入即 extract 兜底落库+emit(侧栏即时非新对话),LLM 成功后覆盖;LLM 调用包 20s 超时,超时/失败保留兜底。— `src-tauri/src/commands/ai/title.rs`(ensure_conversation_title + generate_title_via_llm 外层 timeout)。**配套(历史会话点开重总结)**:`ai_conversation_switch`(commands.rs:1454) 加 `AppHandle` 参数,title 空(`record.title.is_none()`)时 `drop(session)` 后 `get_active_provider` + `spawn_ensure_title` 触发重生成(无 provider 静默跳过,不阻塞切换;ensure 内 title Some 判断防重复)。前端无需改(AppHandle 自动注入)。cargo check src-tauri EXIT 0。

### 💡 2026-06-16 新需求（已分析·待实施）

> 功能需求，已走查定位链路 + 方案记录，待用户确认后实施。

- [ ] F-260616-09 [P1/架构·**A路线✅(batch60 + 06-19核验补漏已落地:commands.rs:1404/useAiConversations.ts:80 → 阶段1跳过),B 设计完成 2026-06-19([F-09设计文档](./02-架构设计/F-09-多会话并发架构设计-2026-06-19.md))·阶段2 批1-8 待拍板 b-1(messages per-conv)启动(见待决策.md)**] — **AiSession 单例 → 多会话并发架构（多窗口来回切 + 并行执行）**。用户需求(3 条消息强化)：「应用级支持同时具备多会话」+「同时具备多会话的能力」+「多个会话窗口来回切，**并行执行**」。现状（Explore 走查确认）：`AiSession` **应用级单例**（`state.rs:164` `ai_session: Arc<Mutex<AiSession>>`，init:205），messages/generating/stop_flag/pending_approvals/agent_language 全一份。设计「单例 + 软隔离」——generating 互斥(同一时刻**只一个对话能跑**) + 切换旧 loop 退出(`agentic.rs:177-190` B-260615-11) + 切换时 generating→readonly(`commands.rs:873-881`)。`llm_concurrency` per_conv 因此退化为单对话内并发(`state.rs:93-96` 注释已标注预留改 HashMap)。**memory 已标记「AiSession 单例未动」架构债**(aichat-arch-extensibility)。**残留 bug**(表象=新建会话上下文残留)：前端 `newConversation`(`useAiConversations.ts:31-41`) 漏清 queue/generatingConvId/agentRound/searchQuery；后端 `ai_conversation_create`(`commands.rs:784-821`) 漏清 agent_language/stop_flag(设 true 停旧 loop 没复位 false)。**用户「并行执行」明确=B 方向**(A 只解切换残留不满足并行)。**分档**：**A 短期隔离修复**(补漏清字段，维持单例软隔离，解残留，低风险立即做，**B 前置**) + **B 多会话并发架构**(单例 → `HashMap<conv_id, SessionState>`，每会话独立 messages/generating/stop_flag/pending_approvals + llm_concurrency per_conv 改真 per-conv HashMap + loop 内校验改 conv_id 索引各自 session + 切换不退出旧 loop 各自跑完真并发 + 事件路由核对 conversation_id 全覆盖)。**决策点(待定)**：(a) A 先做解燃眉 vs 直接 B——倾向 **A 先**(残留真 bug 立即解，B 架构另立项) (b) B 形态——AiSession 整体 HashMap vs 拆字段(messages/pending 已可按 conv reload，只拆 generating/stop_flag per-conv)——倾向**后者侵入小** (c) B 并发上限——复用 llm_concurrency.global 限并发会话数防 token 暴增——倾向是 (d) **多窗口 UI 形态**——(d1) 单面板侧栏会话列表切换+后台并行(改动小) vs (d2) 每会话独立 Tauri 窗口(`useAiWindow.ts` detached 已有基础可扩，改动大但「窗口来回切」语义最贴)——**待用户定**，倾向先 d1 后 d2 (e) 旧 loop 切换处理——不退出各自跑完(真并发，倾向) vs 仍退出。— state.rs(AiSession 去单例化) + commands.rs(create/switch 重构 + 漏清补) + agentic.rs(:177-190 校验改 conv_id 索引) + context.rs(ContextManager per-conv) + useAiConversations.ts(漏清补) + stores/ai.ts(per-conv state) + llm_concurrency per_conv + useAiWindow.ts(若 d2 多窗口)。**B 是大改架构升级，需单独设计文档 + 立项**。**✅ 已决(2026-06-16·待决策归档):a — A 隔离修复 + B 立项(🔥 B 阶段高优先级尽快实施),UI d1+d2 都做(侧栏切换 + 独立 Tauri 窗口)**。

- [ ] F-260616-12 [P2/依赖F-09] — **retry 持 permit 不释放（多会话隐患）**。F-260616-07 落地的流式重试循环（`agentic.rs:238-239`）重试期间持有 global+per_conv permit 不释放（注释「防新请求挤占」）。当前 AiSession 单例 + 主 loop 串行无影响，但 **F-260616-09 多会话并发后**，重试期间阻塞其他对话 LLM 调用。**方向**：多会话落地时核对——主 loop 串行下重试持 permit 防自己挤占无意义，倾向重试不持 permit 或仅持 per_conv。— agentic.rs:238 + F-260616-09 多会话架构。**依赖 F-260616-09 立项后一并处理**。

### 💡 2026-06-16 新需求（UX 交互优化·分析完成·待实施）

> 用户实时反馈的交互优化需求,已走查定位链路+方案记录。

- [x] ✅(F-15 全阶段完成·波3 is_active白名单+push token / 波5 compress_prompt四段式+compress_via_llm+context辅助 / 波6 阶段2手动 2IPC+3事件+前端2按钮 / 波8 阶段3自动 loop顶部检测压缩+降级·主代分波核查 cargo0err+vue-tsc0+df-ai/devflow test passed) **F-260616-15** [P1] — **AI Chat 上下文管理增强：会话分段 + 手动压缩 + 智能裁剪**。三个子需求，详见 [F-15-上下文管理增强设计-2026-06-16.md](./02-架构设计/F-15-上下文管理增强设计-2026-06-16.md)。**阶段1 基础部分 ✅(波3·8a142c2 is_active 白名单+push token 修正)**；阶段1 剩余 ✅(波5·63bff8b compress_prompt四段式+compress_via_llm+context 9辅助方法,幂等/单向不可逆/纯函数/零行为变化,df-ai 107pass)；阶段2 手动功能 ✅(波6·4194842+主代核查,cargo0err+vue-tsc0+devflow79pass,2 IPC ai_chat_clear_context/compress_context+3事件 AiContextCleared/AiCompressing/AiCompressed+前端2按钮+status分组渲染+useAiContext composable,clear标archived_segment不删DB/compress LLM失败不阻塞)；阶段3 自动 ✅(波8·0202b51+主代核查,cargo0err+vue-tsc0+devflow79pass,loop顶部检测budget×0.6+压缩+降级原裁剪,set_compressing成对,延迟mutate失败零副作用)。**F-15 全阶段完成 ✅**。

  **核心问题**：① `ai_chat_clear` 全删 DB 消息不可回溯 ② `build_for_request` 超预算直接丢弃旧消息零保留(`agentic.rs:226` `_trimmed` 被忽略) ③ 无压缩能力。

  **统一方案**：`ChatMessage.status` 扩展两个值 `archived_segment`(分段标记)/`compressed`(压缩标记)，`is_active()` 改正面白名单 `matches!(status, None | Some("active"))`（零行为变化，新状态自动不 active），`sanitize_messages` step 0 已是唯一发送过滤点改 is_active 即隔离旧段/压缩段。`push()`(context.rs:174) 不计 `!is_active()` 消息 token（当前全量计入致 restore 后 token 虚高误判超预算）。

  **① 会话分段**：IPC `ai_chat_clear_context`：当前 active 消息标 `archived_segment` + 扣除 history_tokens + 落库。不插分隔线 system（前端按 status 渲染折叠分隔条）。三元组原子标记（复用 `build_eviction_units` 分组）。

  **② 手动压缩**：IPC `ai_chat_compress_context`：取 active 消息 → `compress_prompt` 四段式(意图/决策/文件/约束) → `provider.complete()` 非流式调用(复用 `title.rs:111` `generate_title_via_llm` 模式 + `build_provider_for`) → 原始消息标 `compressed` + 插入摘要 system。压缩单向不可逆，DB 原始消息保留。

  **③ 智能裁剪（自动压缩）**：agentic loop 循环体顶部 `build_for_request` 前检测：`history_tokens > budget×0.6` 且有可压缩消息 → `compress_old_messages`（保护区外消息 LLM 摘要 → 标 compressed → 插摘要 system）→ emit `AiCompressing`/`AiCompressed`。LLM 失败降级原有裁剪（不阻塞 loop）。`compress_old_messages` 是手动/自动共用的公共函数。幂等：已 compressed 不二次压缩，`is_compressing()` 标志防重入。

  **实施顺序**：阶段1 基础(is_active 白名单+push token 修正+compress_prompt+compress_via_llm+辅助方法) → 阶段2 手动功能(2 IPC+前端按钮+渲染+i18n) → 阶段3 自动(loop 压缩+事件+前端 loading)。阶段1-2 完成用户已有完整能力。

  **涉及文件**：`df-ai-core/provider.rs`(is_active 白名单) + `df-ai/context.rs`(push token 修正+辅助方法) + `commands/ai/prompt.rs`(compress_prompt) + `commands/ai/title.rs 或新文件`(compress_via_llm) + `commands/ai/commands.rs`(2 IPC) + `commands/ai/agentic.rs`(loop 自动压缩) + `commands/ai/mod.rs`(2 事件) + `lib.rs` + `api/ai.ts` + `useAiPanel.ts` + `useAiConversations.ts:79`(过滤扩展) + `useAiEvents.ts` + `AiChat.vue` + `i18n/{zh-CN,en}/aiChat.ts`

### 🔴 aichat API Key 401 排查（2026-06-16）

> 用户报对话失败「调用失败: API Key 无效或无权限」+ 重设 key 无效。**排查结论:代码链路全对(保存 keyring✓ / 读取 resolve_provider_secret✓ / 鉴权头 openai=Bearer·anthropic=x-api-key✓ / URL 智能拼接✓),401 来自服务端,非 devflow bug**。根因四选一(key 无效 / provider_type 不匹配 / base_url 错 / model 名错)。详见 [aichat-apikey-401排查-2026-06-15.md](./09-问题排查/aichat-apikey-401排查-2026-06-15.md)。

- [ ] S-260615-01 — **[待用户确认根因]** 用户跑直连测试(curl)区分 key/provider_type/base_url/model 哪项错,见详情文档「验证步骤」

### 🟦 架构审查待修项（2026-06-16）

> 纯架构层评估（边界/依赖/抽象/扩展性/状态管理），详见 [架构审查-2026-06-15.md](./05-代码审查/架构审查-2026-06-15.md)。共 🔴6 🟡6 ⚪4 + 亮点 6。与 06-14 三份报告去重（不重复 bug/性能 FR-*、aichat AR-*）。

**中期（技术债）**
- [ ] ARC-260615-07 — 其余见文档：df-core 改名 df-types（类型库非核心）/ src-tauri IPC 编排层抽取(df-app，5711 行成事实业务层) / AI agent loop 从 IPC 下沉 df-ai / 3 view 绕 store 调 api / 类型契约 ts-rs 代码生成 / AiSession 多会话(B 路线前置) / AppState 分组 / IPC 命名统一

### P1 — 重要缺陷

- [ ] T-260614-01 — **[P1]** Sprint 9/10/14/15/16/18 多项未 tauri dev 实测 — 评分 IPC 缩放 / update_full / promote_idea / Store getter / token 落库 / 知识库 Tier 1 全栈 / LLM 并发 Semaphore / 知识生命线（#54 跟踪）— source:Sprint 9-18 (06-14)
- [ ] T-260614-02 — **[P1]** 切对话不中断路由：部分场景运行时实测（A 路线场景 2/3） — source:Sprint 8 (06-14)
- [x] ✅(workflow w999qdu86·ai_approve None 分支 match 三路分流对齐 audit_finalize·cargo check EXIT 0 + vue-tsc EXIT 0 + 主代 grep 印证) **BUG-260618-11** — **[P0🔴]** `commands.rs:289` ai_approve 幂等路径 `unwrap_or_default` 吞 DB 错误 — audit.rs 已修(B-260617-17 match 三路分流)，但 ai_approve 的 find_by_tool_call_id 仍 unwrap_or_default。DB 故障→Err 压 None→返回「未找到挂起审批」误导。**修复**: 对齐 audit_finalize 模式 match { Ok(Some)→rec, Ok(None)→return Err, Err(e)→error!+return Err } — source:CR-260618-11 batch69 全面走查 (06-18)。**行为变更**:DB 故障现返回「查询审批记录失败(DB 故障),请重试」明确错误(原误导为「未找到挂起」),两者同属 Result Err 分支调用方语义不变,仅文案更精确可重试。

### 🔴 架构坏味道全面扫描（2026-06-18·6路并行 Explore agent·57K行代码）

> 来源: God文件/长函数 + 循环依赖/耦合 + DRY违反 + 错误处理不一致 + 命名API/类型安全 + 测试覆盖/死代码。6 维度全覆盖。

**🔴 P0 必须修复 (4项)**:

- [x] ✅(workflow w5siwnipj 核验+主代实施·agentic:348/knowledge_inject:130,139,312/idea:292-296 加 tracing::warn 降级不改返回值·audit:344 误报(CR-11 已修三路 match)·commands:290 BUG-11 已修·cargo check EXIT 0 + vue-tsc EXIT 0) **SMELL-P0-1** — **[P0🔴]** `unwrap_or_default` 吞错 **5 高危闭环**。agentic.rs:348(provider 池空走原空池兜底)/knowledge_inject.rs:130,139,312(检索/解析失败走原降级不注入/提炼跳过)/idea.rs:296(tags 坏降级空继续评估)本批 match+warn(空 Vec 零行为变更);audit.rs:344 误报(audit_finalize CR-11 已修三路 match,line 漂移+旧版本);commands.rs:290 BUG-11 已修。**58 处全量排查降 P2 非紧急**(高危已清,余为 Option 取默认/JSON 序列化等低危)。
- [x] ✅(workflow w2xkw4ybh 抽 register_data_tools(18 持 db 工具:list_projects/list_tasks/list_ideas/update_project/create_project/bind_directory/create_task/update_task/advance_task/run_workflow/delete_task/create_idea/delete_project/restore_project/purge_project/list_trash/get_project_count/get_task_count)+ 主代抽 register_file_tools(10 文件工具:run_command/read_file/list_directory/write_file/patch_file/file_info/append_file/delete_file/rename_file/search_files)·build_ai_tool_registry 1091→7 行·加基线测试 test_build_ai_tool_registry_baseline_tool_count(Database::open_in_memory 断言 len()==28 + tool_names() 集合锁定)·主代核查 cargo check --workspace EXIT 0 + cargo test df-ai 119 passed + devflow 基线 1 passed + vue-tsc EXIT 0) **SMELL-P0-2** — **[P0🔴]** `tool_registry.rs:363 build_ai_tool_registry` **1091 行单函数** — 拆分为按功能分组注册函数(register_data_tools 18 db 工具 / register_file_tools 10 文件工具)，build_ai_tool_registry 收敛到 7 行编排 + 基线测试锁定 28 工具数防回归。
- [ ] **SMELL-P0-3** — **[P0🔴]** `AiChat.vue` **4026 行 God 组件** — 拆分: ConversationSidebar(侧栏+搜索) / MessageList(消息列表+流式) / ChatInput(输入框+附件) / ApprovalPanel(审批卡片)。目标单组件 <500 行。
- [x] ✅(workflow wowdnw4ba·tests/shell.rs 5 #[tokio::test] 覆盖成功/非零/超时/env/working_dir + shell.rs:55 过时TODO注释清理·execute逻辑零改动·主代 cargo test -p df-execute 5 passed + cargo check --workspace EXIT 0) **SMELL-P0-4** — **[P0🔴]** `df-execute` crate **零测试**(shell.rs 120行已集成工作流) — 补 shell 命令执行+超时+输出截断基础测试。

**🟡 P1 应该改进 (9项)**:

- [x] ✅(workflow w8774xcev·7 agent 按 crate 全域扫·绝大多数 no-action:生产 unwrap 全在 #[cfg(test)] 或已是安全 expect/unwrap_or(crates 错误处理健康,之前多批重构清理)·仅 df-workflow dag.rs:106/135 两处运行时 unwrap 加 safe 注释(contains 守卫+in_degree 全覆盖不变量)·主代 cargo check --workspace EXIT 0 + cargo test df-workflow EXIT 0) **SMELL-P1-1** — **[P1🟡]** 生产代码 206 处 `unwrap()` 排查——逐处加注释说明为何安全(如"guaranteed by upstream invariant")或改 `expect("msg")`/`?`/`ok_or_else`
- [x] ✅(workflow wuirgcxoy no-action·前提过时:err_str helper 已存在 src-tauri/src/commands/mod.rs:23·纯 .map_err(|e|e.to_string()) 样板 0 残留(仅 mod.rs:22 注释字面量)·101 处已用 err_str·剩 5 处 String 上下文闭包+28 处 anyhow 不同抽象层不应统一·主代 grep 印证) **SMELL-P1-2** — **[P1🟡]** IPC 层 `.map_err(err_str)?` 样板 100+ 处 —— 抽 `fn err_msg(s: &str) -> impl FnOnce(E) -> AppError` 或 Result 扩展方法
- [x] ✅(workflow wuirgcxoy no-action·前提不成立:views 仅 AuditLog.vue:102 直接 invoke·invoke 已统一 src/api 层封装(9 文件)·try-catch-finally 副作用高度异构(scanError/showToast/task=null/递归回退等)无标准 loading/error/invoke 三件套可封装·主代 grep 印证) **SMELL-P1-3** — **[P1🟡]** Vue views try-catch-finally invoke 模式 15+ 处重复 —— 抽 `useTauriCommand<T>(cmd, args)` composable 封装 loading/error 状态
- [x] ✅(workflow w8774xcev 评估·df-nodes Cargo.toml 依赖 5 crate 属实·task_state_machine.rs 零 crate 依赖(仅 HashMap+OnceLock)+单一消费者 advance_task_atomic·拆出不解锁循环依赖·结论缓做:收益不抵成本,触发条件=第二 crate 需复用状态机判定时再拆) **SMELL-P1-4** — **[P1🟡]** df-nodes 聚合点依赖 5 crate —— 评估拆分 df-task-state-machine(状态机从节点实现分离)
- [x] ✅(workflow weckqp9mv no-action·现场核验两半:advance_task_atomic 已在领域层 df-nodes/task_advance_node.rs:43(状态机+CAS+bump_rounds),3 IPC 调用点 task.rs:171/workflow.rs:319/tool_registry.rs:565 纯 thin 转发零逻辑✅已达成;record_to_idea 下沉经依赖图核验为错误方向——df-ideas→df-storage 倒依赖违反六边形架构(IdeaRecord 是 storage DTO,IPC 层是 storage-DTO→domain-entity 映射的正确 adapter 归属)·主代 grep 印证) **SMELL-P1-5** — **[P1🟡]** IPC 层职责泄漏 —— `advance_task_atomic`(workflow.rs) / `record_to_idea`(idea.rs) 转换逻辑下沉 domain 层
- [ ] **SMELL-P1-6** — **[P1🟡]** String 类型滥用替 newtype/enum —— execution_id/status/tool_type 等 5+ 处改强类型
- [x] ✅(workflow weckqp9mv no-action·enum 负收益:archived/pinned 是 bool DB 列直映射,动作式 enum 在 crud 边界每调必 bool↔enum 转换只搬噪声;2 crud+2 IPC 调用者紧邻定义已自解释;真正歧义在前端 store.archiveConversation(id,true) 但正解是拆 archive/unarchive 两 TS 方法(纯前端重构)非推 enum 到 JS 边界;IPC 引 enum 触 Tauri 序列化层 prod runtime 才报错·主代 grep 印证) **SMELL-P1-7** — **[P1🟡]** bool 参数陷阱 —— `set_archived(id, bool)` / `set_pinned(id, bool)` 改 enum
- [x] ✅(workflow wowdnw4ba·6篇文档加「实施状态(2026-06-18核对)」标注段·grep file:line佐证·不改设计正文·主代 grep 6篇标注段全在:B-03:11/AiNode:11/类型对齐:7/F-01:11/F-05:10/secret:10) **SMELL-P1-8** — **[P1🟡]** 架构文档过时 6 篇更新 —— B-03/AiNode自审/前后端类型对齐/F-01/F-05/secret下沉 标注实际实施状态
- [x] ✅(agent crud-split 实施 + 主代兜底核验·crud.rs 2212行→crud/ 6文件(mod/settings/project_repo/task_repo/conversation_repo/idea_repo)·re-export `pub use *_repo::*` 零调用方改动·宏 pub(crate) use + 子模块 use super::impl_repo·基线测试 all_known_tables_have_column_whitelist(12表)+ all_repos_constructible_in_memory(13Repo)·主代 cargo check --workspace EXIT 0 + df-storage 35+11 passed + devflow 96 passed) **SMELL-P1-9** — **[P1🟡]** crud.rs 2212 行按表拆分 —— project_repo / task_repo / conversation_repo / idea_repo 独立文件

**⚪ P2 可选优化 (5项)**:

- [x] ✅(workflow wowdnw4ba no-action·SMELL描述过时:实测run方法151行+2参非描述的282行+8参·SW-01 TOCTOU保留(line136守卫/138-153对称emit)·描述系历史形态已被先前重构改善·销账) **SMELL-P2-1** — run_workflow_inner 282行+8参数 → 配置对象封装 + 阶段子函数提取
- [x] ✅(workflow wowdnw4ba·main.ts app.config.errorHandler + ErrorBoundary.vue onErrorCaptured 降级UI+重试 + i18n error.ts×2中英对称·未嵌入仅提供能力零行为变更·主代 vue-tsc EXIT 0) **SMELL-P2-2** — 前端全局 ErrorBoundary(Vue errorHandler)
- [x] ✅(workflow w8774xcev 评估·**描述修正**:NodeContext 无 metadata 字段(node.rs:13-26),含 metadata 的是 NodeOutput(node.rs:34)·NodeOutput.metadata 死字段(2构造点初始化空 HashMap+0消费者)·ShellRequest.env 同死字段(5构造点全 HashMap::new() 空)·改结构体波及小但纯类型整洁 ROI 低·建议直接删字段或保留,不实施) **SMELL-P2-3** — HashMap<String,String> → 结构体(NodeContext.metadata / ShellRequest.env)
- [x] ✅(workflow w5siwnipj 核验·line 3 模块注释已有「⚠ B 路线占位...有意保留空壳,勿删」三要素齐全·零外部调用·保留 B 路线预留点) **SMELL-P2-4** — coordinator.rs 空壳标注 roadmap **已完成**(line 3 注释),保留非删(B 路线 aichat 决策能力预留,对齐 memory aichat-arch-extensibility)
- [x] ✅(workflow w5siwnipj 核验 7 处+主代补 #6/#7 注释·全部标注完成·cargo check df-ai EXIT 0) **SMELL-P2-5** — #[allow(dead_code)] **7 处全部标注完成**(实际 7 非声称 12,部分已清)。5 处原有注释(prompt.rs:147/compress.rs:45,98 F-15 阶段预留 + openai_compat.rs:81,98 SW-24 反序列化对齐)+ 本批补 anthropic_compat.rs:61 id/:65 stop_reason 注释(对齐 openai SW-24 风格)。全合理保留无真死代码。**另:cargo warning 死字段 4 处**(PendingApproval.risk_level/diff + AppState.releases/node_executions)属另一类未标 allow,待核对。

**🆕 sweep 派生登记（2026-06-18·workflow w2xkw4ybh data 抽出 + wo7d6zdqm 多维度 sweep）**:

- [ ] ARC-260618-01-d [P1/行为变更·待确认产品意图] — **conditions 条件引擎未求值**。`df-workflow/conditions.rs` `ConditionEngine` 零消费(无调用方),`Edge.condition` 字段存储但工作流执行器未求值分支——边条件当前形同虚设。**产品决策点**:① 实现条件求值(executor 在节点完成后求值出边 condition 决定路由)还是 ② 标 `#[allow(dead_code)]` + 文档明示"条件为预留未启用"。涉及行为变更(executor 控制流改变),确认产品意图后再实施。对齐 H 组长期池 T-14-11 条件引擎(line 42 仅指针无详情)。— `crates/df-workflow/src/conditions.rs`(ConditionEngine) + `crates/df-workflow/src/executor.rs`(边求值点)
- [ ] ARC-260618-01-e [P1/行为变更·待确认产品意图] — **adversarial `evaluate_with_llm` 一致性未校验**。`df-ideas/adversarial.rs` `evaluate_with_llm` 返回的 `final_assessment`/`recommendation` 两个字段语义一致性未校验(如 final_assessment=强烈反对 但 recommendation=promote 的矛盾组合无守卫)。LLM 输出存在字段间语义漂移风险。**产品决策点**:① 加一致性校验(矛盾时降级或重评)还是 ② 视为 LLM 自由表达不加约束。涉及行为变更(评估结果可能被改写),确认产品意图后再实施。— `crates/df-ideas/src/adversarial.rs`(evaluate_with_llm 返回结构)
- [ ] SW-260618-21 [P2/待 AiChat 沉淀] — **formatRelativeZh 重命名 formatRelative**。`src/utils/time.ts` `formatRelativeZh` 名带 Zh 后缀但实际非中文硬编码(已读 i18n.global.locale, sweep 批2 time.ts formatDate 已修 CR-260615-08 漏修),函数名误导。4 调用点含 `AiChat.vue`(并发改动中),为避免并发冲突缓做,待 AiChat God 组件拆分(SMELL-P0-3)沉淀后统一重命名。— `src/utils/time.ts`(formatRelativeZh) + 4 调用点(含 `src/components/AiChat.vue`)
- [ ] SW-260618-22 [P2/已知 B-260617-03] — **useAiSend resolveLang DRY**。`useAiSend` 的 `resolveLang` 与其他 composable 语言解析逻辑重复,应抽 `aiShared` 共享。已知登记为 B-260617-03,此处补 sweep 维度记录便于检索。— `src/composables/ai/useAiSend.ts`(resolveLang) → 抽 `src/composables/ai/aiShared.ts`

### 🟡 AI Chat 交互体验改进（2026-06-14 方案 → 待办化）

> 来源：[aichat交互体验改进方案-2026-06-14.md](./02-架构设计/aichat交互体验改进方案-2026-06-14.md)。7 大类 20 个改进项，按原文优先级分 4 批。**去重**：§1.3 复制 ⊂ F-260615-13/14（已写）、§3.4 新建中断 ⊂ B-260615-10（已做软复位，本文档加 confirm 增强）、§6.2 分离窗口同步 ⊂ CR-24/25（已写）。**状态: 待讨论→看板跟踪**。

**第三批 — 锦上添花**

- [ ] UX-2025-12 [P2] — **历史消息分页懒加载**（§2.3）。**▶ 已打开推进（2026-06-17 用户决定不再等痛点，与 UX-19 一并纳入）**。方案：switchConversation 首次加载最近 50 条，滚顶加载更多（需后端 messages 查询加 offset/limit 支持）。关联 B-260629(messages 无上限)。**决策点（待定）——与 UX-19(虚拟滚动)二选一**解决长对话渲染卡顿：分页（后端配合，简单稳定，但切换/搜索跨页体验割裂）vs 虚拟滚动（纯前端，体验流畅，但流式末条挂载复杂）。**倾向 UX-19 虚拟滚动优先**（纯前端零后端改动+体验优），本项分页作为虚拟滚动不足时的补充方案。— src/stores/ai.ts + src/composables/ai/useAiConversations.ts + 后端 conversation.rs

### 待澄清 / A-B 待定

- [ ] S-260614-01 — **[用户已澄清 2026-06-17]** 「显示多开」= 多个会话来回切都可对话(非 AI Chat 窗口多开)。关联 T-260614-02(切对话不中断实测)+ F-09 多会话架构。**A 路线(单例软隔离,已做)**:切会话软复位+DB 加载上下文+active 切换,来回切应能对话(需实测 T-260614-02)。**B 路线(真多会话,未做)**:切走会话 generating 不打断,每会话独立 AiSession(memory aichat-arch-extensibility 单例未动)。**✅ 已并入 F-260616-09(2026-06-16 已决 a):A 路线已实施待实测(T-260614-02),B 高优先立项,详见待决策归档/2026-06.md**
- [ ] S-260614-02 — 审批可见性 A/B 待定 — B-01 修复后 pending_approvals 内存态不再被 switch 清空，前端 `ai_pending_tool_calls` 查询有数据，但 `state.pendingApprovals` 在 AiChat.vue 是否有兜底渲染仍需实测确认。A. 加兜底渲染 / B. 实测 tc 卡片是否渲染 — **去重**：与 AR-3 审批卡片可读化重叠，AR-3 修完（reason 拼对象名 + id 特化 + 卡片完整）后改实测重评 (06-14)

### P2 — 不阻断缺陷 / 增强

- [ ] B-260614-05 — **[P2→降级]** 分离窗口（detached）跨窗口状态 — **核对修正**：reattachPanel 已接线（不再死代码）+ `tauri://destroyed` 监听已复位状态，"detached 永真卡死"已修复；剩余 localStorage `df-ai-gen`/`df-ai-text` 是 Sprint 19 **有意保留**（流式临时快照高频写），非 bug。仅在出现新场景失效时再评估改全局 emit — **去重**：与 AR-M5（跨窗口 state 完全隔离）同类，aichat 审查描述更深 — source:代码审查 + Sprint 19 (06-14)

### 🔧 2026-06-18 6 域并行走查 sweep-fix（workflow wd2fnjh3s·6 agent·主代核查 cargo check --workspace EXIT 0 + vue-tsc EXIT 0）

> 开放式扫模块找新问题 + 修确定性低风险 bug + 记歧义为新 todo。**6 agent 修 12 处 / 新 todo 27 项**(去重后 24 项入此节,3 项重复跳过:conditions 未实现=T-260614-11 / AiSession 单例跨会话 clear=F-260616-09 B 路线 / dead_code 6 处基线 warning 合 1 条)。**主代独立核验**(不信 agent 自报 checkOk):cargo check --workspace EXIT 0(仅 6 pre-existing dead_code warning 基线)+ vue-tsc EXIT 0 + grep 抽查 scan.rs 索引对齐/audit.rs 吞错分流/commands.rs retain 源码形态全正确。**审查防污染拦截 1 瞬态误报**:fe-other agent vue-tsc 抓 ToolCard.vue:653 cmdOutput TS6133 never read,实为 fe-aichat 改 ToolCard 中间态(computed 已加模板未引用)被并发 vue-tsc 抓到,最终态模板 line 122/128 已引用,全量 vue-tsc 复核 EXIT 0 确认非 bug(memory [[review-batching-worktree-transient]] 教训)。

**已修 12 处(主代核查全通过)**:
- df-ai:① context.rs `estimate_message` 累加 parts Text/Image.base64 token(F-05 回归,大图绕过预算致 provider 400/500,加单测 estimate_message_counts_parts_tokens)② anthropic_compat.rs Image 转换 clone→move(DRY/效率)
- df-storage/project/ideas:③ scan.rs `is_pure_badge_line` to_lowercase→to_ascii_lowercase(非ASCII索引错位/panic)④ scan.rs `is_monorepo` workspaces null 误判(.is_some_and(!is_null))⑤ adversarial.rs MockProvider 补 reasoning_content(测试 E0063 编译失败)
- rust-ipc:⑥ audit.rs `audit_finalize` 拆 unwrap_or_default 吞错(Err/None 分流+error 日志,对齐 B-260617-17)⑦ commands.rs `ai_conversation_delete` 补非活跃对话 pending_approvals retain(对齐 switch,修死审批残留)⑧ audit.rs 抽 PENDING_APPROVAL_PLACEHOLDER 常量(防去重漂移误命中)
- fe-aichat:⑨ ToolCard.vue isToolFailure/commandOutput 下沉 computed(复用 parsed,DRY/性能)⑩ AiChat.vue 清 4 处虚拟滚动残留死注释(B-260618-01 已移除)
- fe-other:⑪ Projects.vue onUnmounted 清 _toastTimer(timer-leak)⑫ Knowledge.vue refConvTitle parseContext 两次→一次(冗余 JSON.parse)

**新 todo 24 项**:

🔴 P1(2·均需确认设计意图/跨函数,非立即修)
- [x] ✅(workflow wexu1isx1·cargo check EXIT 0 + cargo test df-workflow 23 passed executor 取消不回归) SW-260618-01 [P1] — **executor Ok 路径取消节点事件/状态不一致(TOCTOU 残留)**。executor 阶段三 Ok 分支:已取消节点(set_cancelled 后 execute 返 Ok)跳过 set_completed 正确,但仍 emit NodeCompleted(:139-144 不在 is_cancelled 判断内)+ 仍 outputs.insert(:145)。对比 Err 分支 :151-157 已对称处理(取消则 emit NodeCancelled 替代 NodeFailed)。结果:前端收到状态机=Cancelled 但事件=NodeCompleted 的矛盾信号。**修法**:把 emit Node Completed 与 outputs.insert 移入 if !is_cancelled 分支。但 outputs.insert 移入涉及下游 inputs 行为变更(当前测试锁定 run Ok=取消不中止工作流),需先确认设计意图:Ok 路径取消到底该不该中止后续层?属行为变更,先确认再改。— crates/df-workflow/src/executor.rs(:131-145)
- [x] ✅(workflow wexu1isx1 + 主代修借用E0502 helper改接&mut session disjoint borrow·cargo check EXIT 0) SW-260618-02 [P1] — **审批占位 tool_result 在 stop/clear/create/delete 清 pending_approvals 时未替换终态文本**。占位「需要用户审批,等待确认」残留在 messages,下次发送会把占位文本喂给 LLM。涉及 5 处 .clear()(commands.rs:415/762/863/1317/1334),需统一 replace_tool_result_content 终态化。属行为变更 + 跨多函数,需设计统一收口(可走 session_state 视图 mod.rs:393 已就位待替换)。— src-tauri/src/commands/ai/commands.rs(:861-869 ai_chat_stop 审批清理分支,同型 5 处)

🟡 P2(8)
- [x] ✅(评估暂缓·url 模式当前不可达(前端 Phase2b 只产 base64)+ anthropic_compat 已 warn;加 commands 层校验需追 parts 全链路(IPC→ContextManager→provider)定校验点有歧义 + 误拒合法 url 风险,待 url→base64 预拉落地后 url 模式变可达再加) SW-260618-03 [P2] — **Anthropic 图片 url 模式静默 400**。url 模式只 warn + 发空 data(必触发 Anthropic 400)。注释声明 url 模式当前不可达(前端 Phase2b 只产 base64),但 url→base64 预拉未落地前前端误传 url 会静默 400。建议 commands 层加 parts 合法性校验:url 片无 base64 直接拒绝。— crates/df-ai/src/anthropic_compat.rs(:356-378)
- [x] ✅(workflow wexu1isx1·KnowledgeEventsRepo::list_recent ORDER BY timestamp 兜底·cargo check EXIT 0 + df-storage test passed) SW-260618-04 [P2] — **knowledge_events 通用 query 硬编码 ORDER BY created_at 崩溃**。impl_repo! 宏 query() 硬编码 ORDER BY created_at DESC,但 knowledge_events 表无 created_at(只有 timestamp)。当前无调用方(均走 list_by_knowledge),误调 state.knowledge_events.query() 触发 SQLite "no such column"。修法:① 加专用 query 带 ORDER BY timestamp ② 重构宏支持 per-table 排序列(架构级)。属行为/架构改不直修。— crates/df-storage/src/crud.rs(:139-163 宏 query + :1592-1611 KnowledgeEventsRepo)
- [x] ✅(评估暂缓·当前 PENDING_APPROVAL_PLACEHOLDER 集中常量+SW-24/CR-01 防漂移有效,内容匹配可靠;改进是未来防御(占位含动态片段)+ 跨 df-ai crate 加 status 字段设计成本高) SW-260618-05 [P2] — **pending 占位靠内容字符串匹配(应改 status/kind 字段)**。find_cached_high_risk_result(audit.rs:464)用 PENDING_APPROVAL_PLACEHOLDER 字符串内容排除 pending 占位,依赖内容相等,若未来占位文本含动态片段(如审批 id)失效。建议 ChatMessage 加 status 字段或 ToolResultKind 枚举(pending/resolved)区分。跨 df-ai crate 需设计。— src-tauri/src/commands/ai/audit.rs(:458-464)
- [x] ✅(主代修·SW-06·vue-tsc EXIT 0) SW-260618-06 [P2] — **ToolCard 双 watch(props.tc.status) 合并**。合并 watch 放原 watch2 位置(approving :552/approvingTimer :553/cmdOutputExpanded :534 三 ref 都已定义避 TDZ——原 watch1 在 :535 immediate 访问 approving/approvingTimer 会 TDZ),加 immediate(原 watch1 语义),callback 内 cmdOutputExpanded(completed 初始化)+ approving 复位(pending_approval 判断)合并。**immediate 边界**:s=初始 status 时 cmdOutputExpanded 仅 completed 设(初始非 completed 无副作用)+ approving 初始 false/approvingTimer 初始 null(if null 跳过 clearTimeout)安全。删原 watch1+watch2,合并 watch 放原 watch2 位置。— src/components/ToolCard.vue
- [x] ✅(主代修·SW-07·vue-tsc EXIT 0) SW-260618-07 [P2] — **AiChat 双 watch(currentText) 合并**。删原 :2040 watch(currentText→onContentChange),并入 :2047 watch(currentText→scheduleStreamParse) 单 callback:先 onContentChange(滚动跟随)后 scheduleStreamParse(rAF 分块),**保持原注册顺序**(:2040 先 :2047 后 → callback 内同序)。:2039 watch(messages.length→onContentChange)不同源保留。无 immediate(两原 watch 均无),无 TDZ(onContentChange :2032/scheduleStreamParse :912 均在前)。— src/components/AiChat.vue
- [x] ✅(主代修·SW-08·vue-tsc EXIT 0) SW-260618-08 [P2] — **AuditLog.vue 全硬编码中文未 i18n**。新建 auditLog 命名空间(en/zh-CN:title/refresh/desc/loading/empty + col×7 + pager×4 + risk×3 + status×6 + decided×2)+ AuditLog.vue 引入 useI18n,template 15 处文本 t() 化 + riskLabel/statusLabel/decidedLabel 改 t(`auditLog.xxx.${val}`)(class 映射 statusClass/riskClass/decidedClass 保留不国际化)。模式对齐 Dashboard/Knowledge/Ideas。— src/i18n/{en,zh-CN}/auditLog.ts(新建) + src/views/AuditLog.vue
- [x] ✅(主代修·SW-09·cargo check df-nodes EXIT 0 无 warning + cargo test 82 passed 0 failed) SW-260618-09 [P2] — **AiNode/AiSelfReviewNode provider 解析+构建 DRY 重复**。抽 2 helper:resolve_and_parse(合并 resolve_provider+parse_params)+ provider_from_params(封装 build_provider 5 行)。AiNode :254-262(9 行)→ 2 行;SelfReview parse(:465-466)+ build(:493-498)→ resolve_and_parse + provider_from_params(中间 task 读保留,p 生命周期跨 task 读正确)。messages/temperature 差异不抽(AiNode 用 p.system_prompt/p.temperature;SelfReview 强约束 Some(0.0)+REVIEW_SYSTEM_PROMPT,各节点自有)。原 resolve_provider/parse_params fn 保留(test 直接调用),helper 上层封装。— crates/df-nodes/src/ai_node.rs(2 helper 新增 + 2 节点 execute 改)
- [x] ✅(主代修·SW-10·cargo check df-ai EXIT 0 无 warning + cargo test df-ai 112 passed 0 failed) SW-260618-10 [P2] — **reqwest Client 构建重复(OpenAI/Anthropic Provider::new)**。抽 `lib.rs::build_provider_client()` pub(crate) fn(connect_timeout 30s + 回退 Client::new + tracing::warn 集中),两 Provider::new 调 `crate::build_provider_client()` 消除 7 行逐字重复。**副作用 grep 核验**:Client import 两文件仍用于 struct 字段(self.client:Client);warn 仍多处用(openai:503/anthropic:183/330/364/497/521/535);Duration import 仍用于 :484/:481 timeout。纯 DRY 抽函数零行为变化。— crates/df-ai/src/lib.rs(+14) + openai_compat.rs(-8+2) + anthropic_compat.rs(-6+2)

⚪ P3(14)
- [x] ✅(评估暂缓·当前构造器保证非空不可达,外部 struct 字面量非预期路径,加 normalize 属未来防御 ROI 低) SW-260618-11 [P3] — **OpenAI 图片构造防御缺失**。Image{base64:Some, media_type:None} 或 url+base64 双空退化为空 image_url.url(400)。当前构造器保证非空故不可达,外部 struct 字面量构造缺防御。可加 ContentPart::normalize。— crates/df-ai/src/openai_compat.rs(:345-358)
- [x] ✅(评估确认·设计合理:archived 状态变动需动 updated_at 排序,pinned 排序走 set_pinned 专用方法不碰 updated_at 保值语义;非 bug) SW-260618-12 [P3] — **ai_conversations 白名单 pinned 与 archived 不对称**。白名单含 archived 不含 pinned,启用 pinned 会动 updated_at 破坏置顶时间语义。建议先确认产品意图(或把 archived 也移除强制走专用方法)。— crates/df-storage/src/crud.rs(:372-375)
- [x] ✅(评估暂缓·当前文档约定+测试守护够,内部 normalize 需改 crud 宏影响面大 ROI 低) SW-260618-13 [P3] — **find_path_conflict 跨层规范化靠文档约定**。期望调用方先 normalize_path,第三个调用方漏掉则静默误判。可选内部 normalize 或文档+测试守护。— crates/df-storage/src/crud.rs(:681-695)
- [x] ✅(评估不做·注释已承认权衡:误读为 0=未达阈值无副作用,迁 app data dir ROI 低) SW-260618-14 [P3] — **keyring failcount sidecar 用 current_dir 跨启动不稳**。注释已承认权衡(误读为0即按未达阈值无副作用)。若要稳定需迁 app data dir。— crates/df-storage/src/secret.rs(:34-38)
- [x] ✅(主代修·SW-16·cargo check --workspace EXIT 0 + cargo test -p devflow 92 passed 0 failed) SW-260618-16 [P3] — **F-05 去重审计 status 固定 completed 不透传 rejected/failed**。find_cached_high_risk_result 加 audit_repo 参数 + 改 async + 返回 Option<(String,String)>(content,status);命中旧 tool_result 后查 AiToolExecutionRepo::find_by_tool_call_id(old_id)(**已存在** crud.rs:1256,复用零新增方法)拿缓存来源真实 status(completed/rejected/failed),透传 audit_tool_call 替代固定 "completed";审计记录缺失/查询失败 fallback completed(不阻塞去重降级原行为)。调用点 :625 拆 (cached,status)+.await / :639 &status。审计语义与结果内容一致,防"rejected/failed 结果记 completed"误导安全追溯。— src-tauri/src/commands/ai/audit.rs(find_cached 签名+返回+status 查询 / process_tool_calls :625/:639)
- [x] ✅(评估不做·破坏 UX-260616-03 记忆态设计) SW-260618-17 [P3] — **ToolCardList 4 Set 跨会话不重置累积**。**评估结论:不做**。userExpandedCards/userCollapsedCards 是 UX-260616-03「用户主动展开/折叠记忆态」设计(切回 A 恢复展开记忆=feature 非 bug);expandedCards/expandedTools 已被 collapseInactive 按 activeIds 过滤清。切会话清 Set 会破坏记忆态。「内存慢增长」是记忆保留代价(tc.id 全局唯一 UUID 短字符串,描述自承认「无错配」),可忽略。— src/components/ToolCardList.vue
- [x] ✅(主代修·SW-19·vue-tsc EXIT 0) SW-260618-19 [P3] — **combineAndTruncateLines 与 cmdOutput 合并逻辑冗余**。抽 `combineOutputs(stdout, stderr)` 共享函数,combineAndTruncateLines(:472)与 cmdOutput computed(:652)都复用。逻辑收敛,行为不变。— src/components/ToolCard.vue(:463 函数 + :472/:652 调用)
- [x] ✅(主代修·SW-20·vue-tsc EXIT 0) SW-260618-20 [P3] — **Ideas 本地 parseTags 与 store 版 DRY 重复**。删本地 parseTags(IdeaRecord),import stores/knowledge.ts parseTags(tags:string|null),3 调用点改传 .tags(currentIdea.tags / i.tags)。复用 store 版,签名收敛。— src/views/Ideas.vue(:214 import + :148/:149/:278 调用)
- [x] ✅(评估暂缓·chip/tag 数量小,O(n) filter + 模板重复求值影响可忽略,优化 ROI 低) SW-260618-22 [P3] — **Knowledge getCategoryCount+parseTags 模板重复求值**。getCategoryCount v-for 每项 O(n) filter,parseTags 同模板调 2 次。chip/tag 数量小影响可忽略,优化项。— src/views/Knowledge.vue(:314-317 + 132-133)

#### ℹ️ 架构观察（INFO · 2 项·不进修复队列）

- [ ] **UX-260617-28 [INFO]** — **双监听器同通道 fragility** — useAiEvents + useAiContext 各自 listen('ai-chat-event')，人工协调防双重处理(AiCompressing flag)非架构保证。未来新增事件处理可能触发双重 bug。长期考虑单一分发器模式。— src/composables/ai/useAiEvents.ts:269 + src/composables/ai/useAiContext.ts:85-105

### 🔧 2026-06-18 模型能力维度（cost_tier/intelligence）数据无客观依据·定位完成待实施

> session-role-diagnose-only·铁证定位（不臆想）。用户追问"高/中/低价、标准/增强怎么定的"，定位根因后用户决策：**API 无判别依据的维度不要瞎填/写死，去掉**。

**根因（铁证：`model_fetch.rs:247-259` ModelsList struct）**：provider `/v1/models` API 仅返回 `{data:[{id}]}`，**不含 cost_tier/intelligence/modalities/capabilities/context_window 任何能力判别字段**。这 5 维度 100% 由 `model_probe::probe()` 填：预设表（`presets/models.json` 写死 16 条）+ 启发式（模型名关键词猜）。零客观依据、必然过时、对新模型全错。

**症状实证**：GLM-5.2（旗舰）被启发式猜成 standard/medium（名不沾 4o/lite 词素走默认兜底）；GLM-4.5 被模糊匹配命中 glm-4 → plus/medium（低估）。

**路由副作用**：`router.rs` 用 cost/intel 做硬过滤（步骤4 `intelligence >= min_intelligence`、步骤5 `cost_tier <= max_cost`）+ 排序（步骤7 `Reverse(cost_tier)`）。`title.rs`（max_cost=Low）/ `compress.rs`（max_cost=Medium）按错误档位过滤，可能选不到合适模型。

**用户决策（2026-06-18）**：API 无判别依据的 cost_tier/intelligence → 去掉，不写死不瞎填。（曾尝试补预设表硬编码 GLM 新模型，用户否决：模型数据不该写死进代码会过时。已回滚清理，`git diff` 证两文件零残留。）

- [x] ✅(workflow wexu1isx1·router删min_intelligence/max_cost字段+步骤4/5 filter+步骤7 Reverse(cost_tier)+9调用点+test改写·cargo check EXIT 0 + cargo test df-ai 109 passed) **B-260618-03 [P1] 后端路由解耦 cost_tier/intelligence** — `router.rs` 去步骤4（min_intelligence 过滤）/步骤5（max_cost 过滤）/步骤7（`Reverse(cost_tier)` 排序）→ 排序纯 weight。TaskRequirements 的 min_intelligence/max_cost 字段 + 8 调用点（`title.rs` max_cost=Low / `compress.rs` max_cost=Medium / `agentic.rs`·`project.rs`·`ai_node.rs`·`knowledge_inject.rs`·`adversarial.rs`·df-ideas 的 min_intelligence）同步清理。因 API 无判别依据这俩维度数据不可信，不应参与硬路由。**属主链路由行为变更**，需 cargo test router 全验证 + 核对 title/compress 选模型不退化。— `crates/df-ai/src/router.rs` + 调用点
- [x] ✅(workflow wexu1isx1·heuristic_infer cost/intel 中性默认 Medium/Standard + presets/models.json 17条删两字段·cargo check EXIT 0 + cargo test df-ai 109 passed) **B-260618-04 [P2] model_probe 去瞎填 + 预设表机制存废** — `heuristic_infer` 的 cost/intel 猜测（`model_probe.rs:158-163`）+ `presets/models.json` 两字段，改中性默认（medium/standard）或标记"无依据"。预设表（16 条写死）随厂商发新模型必过时，存废待定。注：modalities/capabilities/context_window 同 API 不返回也瞎填，但功能性维度（vision/embedding/code_gen）有路由意义（选错致 vision 任务选纯文本模型），性质不同，保留但需 UI 暴露用户可改（另立）。— `crates/df-ai/src/model_probe.rs` + `presets/models.json`
- [x] ✅(主代修·UX-04·vue-tsc EXIT 0 + grep 核验无 tagCost/tagIntel 残留) **UX-260618-04 [P2] 前端删 cost/intel 标签** — 自主裁决全删(用户已全局决策去掉 cost/intel·B-260618-03 + 独立于后端路由 + grep 确认仅 2 组件引用 + 回退易)。ProviderPanel.vue 删 tag-cost/tag-intel 两 span;AiChat.vue modelOptionLabel 删 intel/cost 拼接只返 label/model_id;i18n en/zh settings.ts 删 tagCost/tagIntel 命名空间 + 注释 4 维度→模态+能力。— `src/components/{settings/ProviderPanel,AiChat}.vue` + `src/i18n/{en,zh-CN}/settings.ts`
- [x] ✅(workflow wexu1isx1 + 主代修前端 types.ts 删 'free'·CostTier 枚举删 Free + test 同步·cargo check EXIT 0 + cargo test df-ai-core 24 passed + vue-tsc EXIT 0) **B-260618-05 [P3] CostTier::Free 死档 + 枚举清理** — 预设表 0 条 free + 启发式从不赋 Free（只写 low/medium/high）。Free 变体形同虚设（router 测试 `same_weight_picks_cheaper_cost_tier` 用到但实际数据不产生）。B-260618-03 解耦后评估删 Free 变体 / 收敛 CostTier 枚举。— `crates/df-ai-core/src/model.rs`

### 🔧 2026-06-18 AI Chat markdown 表格布局破坏（确定性 CSS bug·未实施）

> 用户截图反馈"展示样式有问题"，分析截图（AI 回复测试报告 markdown 表格 + 连续失败重试卡片）。session-role-diagnose-only，定位根因 + 修法。

**核心问题**：markdown 表格（测试报告 #/组件/触发条件/代码位置 四列）**行列对齐丢失、单元格堆叠换行**。

**根因（确定性 CSS bug）**：AiChat.vue:3366-3371
```css
.ai-msg-bubble.ai-md :deep(table) {
  display: block;        /* ← 破坏表格布局上下文 */
  max-width: 100%; width: max-content; overflow-x: auto;
}
```
`display:block` 让 table 失去 `display:table` 的表格布局上下文 → 内部 thead/tbody/tr/td（table-row-group/row/cell）脱离 table 上下文 → **行列对齐丢失，单元格塌成块级堆叠**。视觉分析的"表格列间距/行高/列标题与行叠放/代码位置文字溢出换行"全是此根因表象（非独立间距问题）。注释意图（UX-260617-21）是"宽表格横向滚动不撑破气泡"，但 display:block 实现破坏了表格结构。

**修法（确定性）**：移除 `display:block`，改**父容器滚动**保持 table 布局：
```css
.ai-msg-bubble.ai-md { overflow-x: auto; }   /* 气泡横向滚动 */
/* :deep(table) 不设 display，保持默认 table 布局 */
```
或 markdown 渲染给 table 包 `<div style="overflow-x:auto">`（更可控，不依赖气泡 overflow）。

**附带观察**：连续失败重试卡片（1/4 2/4 3/4）堆叠密集——但这是**连续失败重试的功能现象**（BUG-260617-01 `||true` 已修 924158c；截图若仍复现需另查重试逻辑），非纯样式。

- [x] ✅(workflow w5siwnipj 核验两处 display:block(AiChat.vue:3366 局部 + ai-md.css:59 全局根因)+主代实施·移除两处 display:block/overflow-x:auto·table 恢复默认 display:table 列对齐·气泡 .ai-msg-bubble--ai.ai-md 加 overflow-x:auto 承载宽表格滚动·vue-tsc EXIT 0) **🚨 B-260618-06 [P1]** — **markdown 表格 display:block 破坏布局**。两处根因：全局 ai-md.css:59 + AiChat.vue:3366 局部覆盖。display:block 让 table 失去表格布局上下文 tr/td 塌堆叠丢列对齐（截图测试报告表格错乱根因）。修法：两处移除 display:block（overflow 在 display:table 下浏览器忽略一并移除），table 恢复默认 display:table + max-width:100%，宽表格横向滚动交气泡容器 .ai-msg-bubble--ai.ai-md overflow-x:auto 承载（CSS spec table-display 下 overflow 须由 block 父容器）。— src/styles/ai-md.css(:59) + src/components/AiChat.vue(:3366 UX-260617-21 方案修正)

### 🔧 2026-06-18 aichat 工具结果渲染核对（agent 全量核对·session-role-diagnose-only）

> agent 核对 ToolCard.vue 各工具结果渲染 vs 后端 tool_registry.rs 返回结构，找"结果字段被丢弃/显示不全/裸 JSON"。**search_files 已修**（body 文件列表 + bar pattern，本轮）。余 11 条待修。

**P0**
- [x] ✅(workflow w2drz3ppo 核验+主代实施 run_workflow 显 execution_id 删误导 workflowHint·vue-tsc EXIT 0) UX-260618-05 [P0] — **run_workflow 结果丢 execution_id**。header 硬显 workflowHint「请到工作流页面运行」(误导，工作流已触发)+ body 走裸 JSON。后端返 `{task_id, target_status, execution_id, status, note}`，核心 execution_id 没渲染，用户无法对应执行实例。修：加专门 case 显 execution_id / task_id→target_status，删误导 workflowHint。— `ToolCard.vue`(:809 toolResultSummary + formatToolResult default)

**P1**
- [x] ✅(workflow w2drz3ppo+主代 patch_file 模板分支渲染 diff 红绿行+resultDiffLines computed·vue-tsc EXIT 0) UX-260618-06 [P1] — **patch_file 丢 diff**。后端 `generate_diff` 产完整 unified diff(tool_registry.rs:1148)，formatToolResult 只显「已修改 path(±N 字节)」，diff 整个丢弃，用户无法验证 AI 改了什么。修：加专门 body 分支复用 write_file 审批卡 diffLines 红绿行渲染。— `ToolCard.vue`(:424)
- [x] ✅(workflow w2drz3ppo+主代 delete_file formatToolResult+toolResultSummary 加 backup_path·vue-tsc EXIT 0) UX-260618-07 [P1] — **delete_file 软删丢 backup_path**。只显「已软删除(已备份)」，backup_path(回收站文件名)不显，用户不知从哪恢复。修：补「已备份至 {backup_path}」。— `ToolCard.vue`(:418)
- [x] ✅(workflow w2drz3ppo+主代 read_file 模板 has_more 显「shown/total 行,已截断」·vue-tsc EXIT 0) UX-260618-08 [P1] — **read_file 丢截断提示**。>500 行默认截断返前 500 行，bar 只显 lines·size，has_more/returned_lines 丢弃，用户不知是截断。修：has_more=true 时显「显示 returned_lines/lines 行，已截断」。— `ToolCard.vue`(:72-82)
- [x] ✅(workflow w2drz3ppo+主代 advance_task 加 review_rounds 退回累加(>0 显)·vue-tsc EXIT 0) UX-260618-09 [P1] — **advance_task body 裸 JSON**。后端返完整 TaskRecord(~15 字段含 review_rounds)，formatToolResult 只显「任务 id 推进至 status」，body 走裸 JSON。修：body 显 status + review_rounds(退回累加值)。ToolResult type 缺 review_rounds 字段需补。— `ToolCard.vue`(:414)
- [x] ✅(workflow w2drz3ppo+主代 list_trash toolResultSummary case 返计数,被 UX-12 list_* 重写吸收·vue-tsc EXIT 0) UX-260618-10 [P1] — **list_trash 缺 header 摘要**。toolResultSummary 覆盖 list_tasks/projects/ideas 漏 list_trash，折叠态无计数。返回结构与 list_projects 同构 `{items,total,has_more}`。修：补 case 返计数。— `ToolCard.vue`(:769)

**P2**
- [x] ✅(workflow w2drz3ppo+主代 list_directory 模板 truncated 显「已截断,仅前 1000 项」·vue-tsc EXIT 0) UX-260618-11 [P2] — **list_directory 丢 truncated**。达 1000 条上限截断返 truncated:true，前端无提示。修：truncated=true 显「已截断，仅前 1000 项」。— `ToolCard.vue`(:85-104)
- [x] ✅(workflow w2drz3ppo+主代 list_* 重写修 Array.isArray 对象误判+结构化列表+file_info modified+write_file old_size/encoding+rename_file cross_volume/bytes_moved·vue-tsc EXIT 0) UX-260618-12 [P2] — **list_*  body 全裸 JSON + 各工具零散字段丢失**。① list_projects/tasks/ideas/trash body 走裸 JSON(items 完整 Record)，应加专门 body 分支列表渲染 name+关键字段；② file_info 丢 modified；③ write_file 丢 old_size/encoding；④ rename_file 丢 cross_volume/bytes_moved。ToolResult type 缺 execution_id/note/modified/old_size/cross_volume/bytes_moved/truncated 等字段需补。— `ToolCard.vue`(formatToolResult + 各 body 分支 + ToolResult type :181)

### 🔧 2026-06-18 列表摘要 Markdown 语法字符泄露（三类列表·session-role-diagnose-only·未实施）

> 用户报：AI Chat 对话列表、知识库列表、待审核项目列表的摘要信息都显示 Markdown 语法字符（`**`/`#`/`` ` ``/`-`/`>`）。走查定位链路 + 记 todo，不改代码。

**核心问题**：列表/卡片摘要字段用 `{{ }}` 纯文本插值，但数据源是 AI 生成内容（含 Markdown 语法），既未渲染富文本也未剥离语法字符 → 裸字符外露。详情页都正确 `v-html` 渲染，列表层缺处理。

| # | 位置 | 代码 | 数据源（AI 生成·含 MD） | 详情页对照 |
|---|------|------|------------------------|-----------|
| 1 | 知识库列表 | `Knowledge.vue:71` `{{ item.content }}` | AI 从对话提取的知识内容 | `:120` 已 `v-html="renderedContent"` ✅ |
| 2 | 项目卡片 | `Projects.vue:148` `{{ project.description }}` | `aiScan` 填的 LLM 描述（`:256`） | 无详情渲染 |
| 3 | 灵感列表（pending 待评审） | `Ideas.vue:47` `{{ idea.description.slice(0,60) }}` | 灵感描述 | `:63` 已 `v-html="renderedDesc"` ✅ |

**AI Chat 对话列表（存疑待核）**：侧栏对话项只渲染 `{{ conv.title }}`（`AiChat.vue:49/122/171`），无摘要字段。若用户所说"摘要"即标题，则需核后端自动生成标题（`title.rs` `generate_title_via_llm`）是否产出 Markdown 语法——前端 `{{ }}` 会原样显示。倾向标题本应纯文本，若 LLM 偶发带 `**` 等需后端 prompt 收口或前端 strip。

**根因**：列表摘要本应纯文本预览，三处均走 `{{ }}` 插值，AI 生成源文本含 Markdown 未剥离。

**修法方向（待实施·二选一）**：
- **A 推荐**：加 `stripMd(text)` util（剥离 `**/__/_/#/`/`` ` ``/-/>/\[]()/!\[]()` 等语法留纯文本），列表摘要统一过滤。列表空间小 + `-webkit-line-clamp` 截断（Knowledge 卡片）+ 富文本样式不可控，剥离比渲染更合适。
- B：列表也 `v-html` 统一渲染——不推荐（卡片截断 + 富文本样式溢出/`<p>` 撑高）。

- [x] ✅(主代串行·规避 AiChat God 文件·新建 src/utils/markdown.ts stripMd 剥离 MD 语法(粗体/斜体/标题/代码/列表/引用/链接/图片)+ Knowledge.vue:71/Projects.vue:148/Ideas.vue:47 三列表摘要接入·修自引入注释含 */ 致 JSDoc 提前闭合 TS1011/1127·改安全措辞·vue-tsc EXIT 0) **UX-260618-13** [P2] — **列表摘要 Markdown 语法字符泄露**。三处列表摘要 stripMd 过滤。**AiChat 标题 strip 子项**:规避 God 高频文件·留待并发期后(待核 title.rs 是否产 MD)。— `src/views/{Knowledge,Projects,Ideas}.vue` + `src/utils/markdown.ts::stripMd`

---

### 🔧 2026-06-18 用户实测·aichat bug 反馈(review 会话 2 agent 并行审查·独立 grep/read 核验·session-role-diagnose-only·待实施)

> 用户实测报 2 bug。review 会话派 2 agent 并行核验源码,根因 + 修法定位完成,待主代实施。

- [x] ✅(workflow wwchro468 general-purpose agent 实施 6 文件 + 主代兜底核验·前端 ToolCard.vue advance_task 渲染取 r.title||r.id(:474/:891)+TASK_ID_TOOL_ARG 映射(:799)+displayArgValue task_id→title(:834)+i18n zh/en aiTool.ts;后端 audit.rs resolve_task_label(查 tasks 表 :151 三臂对齐 resolve_project_label)+build_approval_reason advance_task 分支(:173)+tool_registry.rs 删 tool_display_hint advance_task 项(:1575 让 :1549 模板生效)·主代 cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证) **UX-260618-14 [P1]** — **advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。①审批卡 `ToolCard.vue:800` `PROJECT_ID_TOOL_ARG` 未登记 advance_task → id 裸显 UUID;②审批 reason `audit.rs:163` tool_display_hint 短路致 `tool_registry.rs:1546` 模板死代码 + `:172` 任务 id 误查 projects 表;③结果 `ToolCard.vue:477` 模板取 r.id(UUID)非 r.title(TaskRecord 有 title `models.rs:57`),"空白"=UUID 不可读非字段缺失。**修法 B(最快)**:ToolCard:477 取 r.title fallback r.id + i18n 改 `任务「{title}」已推进至 {status}`;**A1** 登记 TASK_ID_TOOL_ARG(advance_task/run_workflow)id→title 回显;**C** 删 `tool_registry.rs:1572` tool_display_hint 让 :1546 模板生效 + audit.rs 加 resolve_task_label。附带:`:1546/:1547` 模板均死代码 + audit.rs:172 任务 id 误查隐患。— `src/components/ToolCard.vue`(:474-478,800-825) + `src-tauri/src/commands/ai/{tool_registry.rs:1546/1572,audit.rs:163/172}` + `src/i18n/{zh-CN,en}/aiTool.ts`

- [x] ✅(agent ux15-batch1 方案A实施 3 文件 + 主代兜底核验·stream_recv 5处重试路径移除AiError emit + agentic Fatal/耗尽单emit AiError + useAiEvents AiStreamRetry首次创建+后续更新·主代 cargo check EXIT 0 + cargo test df-ai 119 passed + vue-tsc EXIT 0 + grep核验5处emit判定 + 修正注释残留 :251/:299) **UX-260618-15 [P1] 第一批(方案A根治N+1)** ✅ — **流式失败重试(1/4 2/4 3/4)每轮独立气泡**。N+1 降为「1 重试气泡 + 1 最终错误气泡」。**第二批(方案C 聚合气泡显示错误原因)可选增强待定**。后端每轮重试 emit AiError(`stream_recv.rs:200/258/298/358/398` 5 处 InitFailed 必 emit),前端 `useAiEvents.ts:326` AiError case 每次 push 新气泡无去重;`AiStreamRetry`(:177)本意更新末条合并但被新 AiError 破坏 → N+1 独立气泡。`agentic.rs:262` 注释误以为 stream_llm 只 emit 一次(实多次)。**用户诉求升级**:不只是修 bug,要**聚合显示设计**(重试 1→4 轮过程聚合单气泡 + 最终结果,勿一排错误吓人)。详见 review 会话重试聚合设计方案(3 角度论证:可行性/前后端一致性/架构整合)。**修法 A(推荐)**:`stream_recv.rs` 5 处 InitFailed 移除 AiError emit(改纯返 retryable,emit 权交调用方)+ `agentic.rs` 重试 loop 只 emit AiStreamRetry、耗尽/Fatal 才 emit 最终 AiError。影响仅流前失败重试路径(Fatal/MidStream 保文/正常错误/压缩失败不受影响)。— `src-tauri/src/commands/ai/{stream_recv.rs:200/258/298/358/398,agentic.rs:207-300}` + `src/composables/ai/useAiEvents.ts:177/326`

- [x] ✅(主代串行·doc-discovery 源码核验 `time.ts:44` 已 `(i18n as any).global.locale.value === 'en'`·CR-260618-24 修复闭环 + CR-260618-25 A-time 复审 ✅ 双印证·漏销账补登) **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value` → `(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44`

- [ ] **UX-260618-17 [P1🟡]** — **ProjectDetail.handleApprovalMulti 漏 submitting 复位(防双击破口)**(CR-260618-25 前端审查发现·Agent B 对抗核验)。`ProjectDetail.vue:431-437` handleApprovalMulti 无 submitting set true/finally,模板 :196-202 只绑 `:disabled="multiDecisions.length === 0"` → 多选审批 approveHumanApproval IPC 进行中按钮不禁用,用户可重复点确认重复触发 IPC。同模板 handleApproval(:417-425)正确 try/finally,handleApprovalMulti 漏对齐。**修法**:顶 `submitting.value = true` + `try { ... } finally { submitting.value = false }`,对齐 handleApproval。— `src/views/ProjectDetail.vue`(:431-437 + :196-202)

### 🔧 2026-06-19 文件拆分升级（3 代理并行分析·建任务·未实施）

> 大文件统计(总 51143 行)+ 3 代理并行分析拆分方案。与已有 SMELL-P0-2(tool_registry)/SMELL-P0-3(AiChat.vue)/SMELL-P1-9(crud.rs) 合并。**通用执行原则**:可见性升级(私有 fn/struct → pub(super))/ 测试跟随被测函数 / **不改逻辑不改 await 边界不改签名** / 三段式验证(cargo check+test+clippy 分 crate,对齐 [[workflow-cargo-timeout-wrap]])/ 保守方案(热路径整块搬不拆函数体)。

**P0(超红线/最脏,本周期优先)**

- [ ] **REFACTOR-260619-01 [P0]** — **commands.rs(ai,1923 行)拆 5 模块**。5 组职责正交:B 发送审批控制(1140,13 IPC)/C 提供商(305)/D 会话CRUD(360)/E 杂项(63)+A helper。**先拆 C `provider_cfg.rs`(零风险试水:仅依赖 ai_providers 无 session 锁/per_conv/spawn)→ D `conversation_crud.rs`(`conversation.rs` 已存在,命名避冲突)→ E `misc_cfg.rs`→ B `chat_control.rs`(最复杂,等 F-09 B 批4 更稳)**。关键:`pub use self::commands::*;` glob 保留(mod.rs:58)否则 invoke_handler 注册断。— `src-tauri/src/commands/ai/commands.rs` + `mod.rs:58`
- [ ] **REFACTOR-260619-02 [P0]** — **anthropic_compat.rs(1025 行)拆模块**。含本会话加的 precheck/summarize(诊断)+ convert_request(159)+ SSE 解析(109)。**保守拆 3 模块**:`types.rs`(请求响应结构体 ~90)+ `sse.rs`(apply_anthropic_event ~160+测试)+ 残留 provider.rs(convert/complete/stream/precheck 留 impl 块不拆,因 Rust impl 不跨文件)。激进拆 4(加 convert.rs)需把 convert_request 等 4 关联 fn 改自由函数(动 6 处 Self:: 调用点),风险高不推荐。— `crates/df-ai/src/anthropic_compat.rs`
- [ ] **REFACTOR-260619-03 [P0]** — **audit.rs(959 行)拆 5 模块**。`audit/{mod,list,reason,finalize,dedup,process}.rs`。`process_tool_calls`(250+行热路径)**整块搬 process.rs 不拆函数体**(锁内 await 边界不动,CR-260618-11#5 性能注记)。11+ 私有 fn 升 pub(super):audit_tool_call/audit_finalize/find_cached_high_risk_result/build_write_file_diff/build_approval_reason 等。`PENDING_APPROVAL_PLACEHOLDER`/`risk_str` 提 mod.rs 共享。— `src-tauri/src/commands/ai/audit.rs`

**P1**

- [ ] **REFACTOR-260619-04 [P1]** — **ToolCard.vue(1527 行)拆 5 子组件+composable+util**。先 `useToolFormat.ts`(纯函数 ~280,零风险)+ `ToolResultBody.vue`(420 最大块);再 ToolCardHeader/ToolApproval/useToolApproval。风险:折叠态 shouldKeepOpen 三处共享 / 审批状态机断链(B-260616-08 回归)/ ToolCardList 批量审批联动。— `src/components/ToolCard.vue` + `ToolCardList.vue`
- [ ] **REFACTOR-260619-05 [P1]** — **agentic.rs(1231 行)抽 agentic_runtime.rs + agentic_stream.rs**。主 loop 是**单函数 720 行不可按函数拆**(工具执行/审批是 loop 内 if 分支;process_tool_calls 在 audit.rs)。先抽 A+E+F `agentic_runtime.rs`(GeneratingGuard+try_continue_agent_loop+ContinueSnapshot,~195,最高收益最低风险)+ B+C `agentic_stream.rs`(StreamOutcome+stream_one_provider,~165)。**D run_agentic_loop 等 F-09 B 批4 落地再评估**(避免 per_conv 双线作战)。— `src-tauri/src/commands/ai/agentic.rs`
- [ ] **REFACTOR-260619-06 [P1]** — **ai_node.rs(1107 行)拆 3 模块**。`ai/{mod,params,ai_node,self_review}.rs`。params.rs(provider 解析 helper ~270)+ ai_node.rs(~130)+ self_review.rs(~300)。测试分块清晰(752/939/1069 三段),低风险。fixture provider_stub/config_with 留 params.rs `pub(super)`。— `crates/df-nodes/src/ai_node.rs`
- [ ] **REFACTOR-260619-07 [P1]** — **(已有 SMELL-P0-3)AiChat.vue(4075)拆 ConversationSidebar/ChatHeader/MessageList/ChatInput**。方案已定,状态/composable 已外移,拆 template+局部 script。前置:先提交工作区未提交改动(本会话 shouldRenderMsg/scroll/1214 预检等)。— `src/components/AiChat.vue`
- [ ] **REFACTOR-260619-08 [P1]** — **(已有 SMELL-P0-2)tool_registry.rs(2023)按功能分组注册函数拆**。register_crud_tools/register_file_tools(已抽)/register_workflow_tools 等,每个 <200 行。— `src-tauri/src/commands/ai/tool_registry.rs`

**P2 暂缓(窗口未到/收益低)**

- [ ] **REFACTOR-260619-09 [P2 暂缓]** — **context.rs(1332)等 F-15 压缩链路稳定再拆**。生产 745+测试 587。impl 跨文件方案(同 crate 多 impl 块,零字段可见性改动)。先 sanitize.rs(最大连续块 ~184)。当前 context.rs 被 F-15/压缩频繁改动,拆分窗口未到。— `crates/df-ai/src/context.rs`
- [ ] **REFACTOR-260619-10 [P2]** — **scan.rs(1015)拆 4 模块**(stack/discover/readme/sample)。纯函数低风险收益低。共享 SAMPLE_IGNORED_DIRS/truncate_chars/read_readme_raw 提 mod.rs。— `crates/df-project/src/scan.rs`
- [ ] **(已有 SMELL-P1-9)crud.rs(2212)按表拆** project_repo/task_repo/conversation_repo/idea_repo。— `crates/df-storage/src/crud.rs`

**执行顺序建议**:01-C provider_cfg(零风险试水) → 02 anthropropic → 03 audit → 04 ToolCard useToolFormat → 05 agentic_runtime → 06 ai_node → 07 AiChat.vue → 08 tool_registry。每步 cargo check+test+clippy 分 crate + 手测。

---

### 🔧 2026-06-19 命令行黑窗修复 + GLM 1214 数据调查（DB 直查定位）

> 用户报两问题:①执行命令行弹黑窗闪烁 ②GLM 1214 messages 非法(多轮)。Build 版无 tracing subscriber 看不到日志,改 DB 直查(`C:\Users\23780\AppData\Roaming\top.1216.devflow\devflow.db`)定位。

- [x] ✅(2026-06-19·df-execute shell.rs 全仓唯一子进程源(grep 确认)·tokio Command 加 creation_flags(0x0800_0000) CREATE_NO_WINDOW·cargo check df-execute EXIT 0 无 warning) **B-260619-01 [P1]** — **执行命令行弹黑色窗口闪烁**。Windows tokio::process::Command 创建 cmd/powershell 子进程默认带控制台窗口(黑窗闪现)。修:`#[cfg(windows)] cmd.creation_flags(0x0800_0000)`(CREATE_NO_WINDOW)。tokio Command Windows 自带 creation_flags 方法(无需 std CommandExt trait)。覆盖 run_command 工具 + 工作流 shell 节点(全经 df-execute)。— `crates/df-execute/src/shell.rs`(:86-93)

- [ ] **B-260619-02 [P1]** — **GLM 1214 messages 非法(数据驱动定位:单条 tool_result 过大)**。DB 直查报错对话 f64dee94:全量 546 条(多数 compressed),**active(发往GLM)仅 7 条结构合法**(system/assistant/user/assistant/user 交替,tool_use↔tool_result 配对 orphan=0,无连续 role,57K 字节 ~19K tokens **未超 GLM 128K**)。**真凶**:单条 tool_result 巨大(read_file 整文件 #2=21843B/#3=16511B/#6=11482B),T-05 截断阈值 50KB 未触发(21KB<50KB),但 **GLM anthropic 端点单条 tool_result content 限制更严(~10-20KB 即拒 1214)**。次要:#4 assistant content 空(len=0 只有 tool_use);"回复一半消失"=1214 在 GLM 流极早(message_start 前)error→stream_recv InitFailed→emit AiError→前端清 currentText,保文逻辑未覆盖。**修法方向**:① tool_result 截断阈值收紧 50KB→8-10KB(read_file/list_directory 大文件截断+提示)② MidStream 早 1214 保文(apply_anthropic_event error 时若已 message_start 不清流式)③ 空 assistant convert 兜底。**已加诊断(待重编译验证)**:anthropic_compat precheck(首条/连续/input/空content/orphan 5 类 Init bail)+ MidStream error 附 messages 摘要(SSE error chunk 塞摘要到前端 raw)+ HTTP/1.1 治 GLM HTTP/2 RST + 错误源链进 anyhow 文案。— `crates/df-ai/src/anthropic_compat.rs`(precheck/summarize/MidStream 摘要/version HTTP_1_1) + tool_result 截断(T-05 `tool_registry.rs`/`audit.rs` 阈值 50K→8-10K) + MidStream 保文(`stream_recv.rs`)

---


### 💡 2026-06-19 新需求（任务关联灵感·已分析·待实施）

> 用户需求：推进任务时能即时、方便地关联到灵感及灵感的对抗式评估等相关信息。当前关联链路是 Idea → promote → Project → Tasks，任务和灵感只能通过项目间接关联，无法直接追溯。

- [ ] **F-260619-01 [P2]** — **任务关联灵感：TaskRecord 新增 idea_id 字段**。推进任务时即时查看关联灵感的描述、多维评分(scores)、对抗式评估(ai_analysis)等信息，辅助决策。

  **数据模型变更**：TaskRecord 新增 `pub idea_id: Option<String>`（关联灵感 ID，可空=未关联），`#[serde(default)]` 兼容旧 JSON。

  **涉及改动（7 处）**：

  1. **迁移 V20**（`migrations.rs`）：tasks 表 `ALTER TABLE ADD COLUMN idea_id TEXT`（nullable，老数据 NULL）；V1 建表 SQL 同步补 idea_id 列（新库直接有）；steps 数组追加 `(20, migrate_v20)`；用 `column_exists` 探测（同 v17/v18/v19 模式），对新库/老库均安全。

  2. **Model**（`models.rs`）：TaskRecord 加 `pub idea_id: Option<String>` + `#[serde(default)]`。

  3. **CRUD 层**（`crud/task_repo.rs`）：`task_from_row` 加 `idea_id: row.get("idea_id")?`；`impl_repo!` 的 insert/update SQL 加 idea_id 列 + params 占位；`list_active` / `list_deleted` / `advance_status_atomic` 的显式 SELECT 列表补 idea_id。

  4. **白名单**（`crud/settings.rs`）：tasks 白名单加 `"idea_id"`（允许 `update_field` 改关联）。

  5. **命令层**（`commands/task.rs`）：`CreateTaskInput` 加 `pub idea_id: Option<String>`；`create_task` 构造 TaskRecord 时写入 idea_id；`update_task` 对 idea_id 做跨表存在性校验（对标 project_id 校验模式：查 ideas 表确认存在，空值=解除关联允许通过）。

  6. **AI 工具层**（`commands/ai/tool_registry.rs`）：`create_task` 工具 schema 加 idea_id 可选参数；`update_task` 工具 schema 加 idea_id（描述说明可关联灵感）。

  7. **前端**：任务创建表单加灵感选择器（下拉选 ideas 列表，可空）；任务详情页加关联灵感卡片（只读，展示灵感标题 + 描述摘要 + 多维评分 scores + 对抗式评估 ai_analysis + promoted_to 状态）；`update_task` 支持 idea_id 字段更新（空值解除关联）。前端获取方式：任务详情展开时若 idea_id 非空，调 `list_ideas` 或新增 `get_idea_by_id` IPC 拉取关联灵感记录渲染卡片。

  **验收标准**：① 创建任务时可选关联灵感；② 任务详情页展示关联灵感的描述 + 评分 + 对抗式评估；③ 可更新/解除关联（update_task idea_id = "" 清空）；④ 老任务（idea_id NULL）无回归；⑤ `cargo check --workspace EXIT 0` + `vue-tsc EXIT 0`。

  — `crates/df-storage/src/{models.rs,migrations.rs,crud/task_repo.rs,crud/settings.rs}` + `src-tauri/src/commands/{task.rs,ai/tool_registry.rs}` + 前端任务组件

---

### 💡 2026-06-19 新需求（MCP Server·灵感来源·待实施）

> 灵感来源：`9ffa5f15` DevFlow MCP Server（对抗式评估 7.2/10，推荐 with resources）。
> 评估关键结论：技术切入点和研发性价比极佳（ToolDefinition ↔ MCP schema 几乎一致，转换成本极低），但反方强度 0.78——暴露 delete_project 等破坏性操作缺乏鉴权/确认/回滚，外部 AI 幻觉可能导致不可逆误删。**必须在协议接入的同时做工具集安全降级**。

- [ ] **F-260619-02 [P2]** — **DevFlow MCP Server：对外暴露任务/项目/灵感管理能力**。以 MCP（Model Context Protocol）server 形式对外暴露 DevFlow 数据层工具，让外部 AI 客户端（Claude Code / Cursor / Cline）直接读写 DevFlow 的项目、任务、灵感数据。

  **形态**：独立 stdio MCP server 子命令（方案 A）。`devflow mcp-server` 启动，应用未运行时也能被 Claude Code 拉起。

  **协议实现**：
  - `initialize` → 返回 server info + capabilities(tools)
  - `tools/list` → 从 AiToolRegistry::tool_definitions() 转换为 MCP Tool 格式
  - `tools/call` → 路由到 handler，返回 `content: [{ type: "text", text: JSON }]`
  - 推荐用 rmcp（官方 Rust SDK）或最小化手写 JSON-RPC 2.0 over stdio
  - MCP Tool schema 与现有 ToolDefinition 结构几乎一致（name/description/parameters JSON Schema），写一个 `tool_definition_to_mcp()` 转换函数即可

  **工具集（首版只暴露数据层，不暴露文件系统工具）**：
  - 项目：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

  **⚠️ 安全降级（对抗评估反方核心风险，必须同步实施）**：
  - **High 风险操作（delete/purge/run_workflow）默认拒绝** + 返回「请在 DevFlow 应用内执行」——防外部 AI 幻觉导致不可逆误删（反方强度 0.78 核心论点）
  - Medium 风险操作（create/update）默认允许 + 日志审计（写 ai_tool_executions 表留痕）
  - 可选 `--read-only` 标志仅暴露 list/get 工具
  - **不暴露文件系统工具**（read_file/write_file/delete_file 等），避免绕过应用内路径校验
  - 仅 stdio / 127.0.0.1，不暴露网络
  - 不引入 MCP 客户端功能（DevFlow 只做 server）

  **数据库访问**：
  - stdio server 启动时打开同一 SQLite（`app_data_dir/devflow.db` 或 `--db` 参数指定）
  - **SQLite WAL 模式确认**：需确认 df-storage 已启用 WAL（反方论点：并发锁竞争未处理）。若未启用 WAL，首版要求关闭 DevFlow 再用 MCP server，或 MCP server 只读连接

  **配置与发现**：
  - `devflow mcp-server --print-config` 输出 Claude Code / Cursor 的 MCP 配置 JSON 片段
  - Claude Code 接入：`claude mcp add devflow -- devflow.exe mcp-server`

  **涉及文件**：
  - 新建 `crates/df-mcp/`（Cargo.toml / src/{server.rs, tools.rs, protocol.rs}）
  - 修改 `src-tauri/src/main.rs`（CLI 参数解析，`mcp-server` 子命令分支）
  - 修改 `src-tauri/Cargo.toml`（加 df-mcp 依赖 + clap）
  - 可选：`ai_tools.rs` 把 handler 闭包抽为可独立复用函数

  **验收标准**：
  1. Claude Code 配置后能 `list_projects` / `list_tasks` / `create_task` / `advance_task` 成功
  2. 应用运行时 MCP server 也能读写（WAL 并发安全）
  3. High 风险操作（delete/purge/run_workflow）被拒绝并返回友好提示
  4. `cargo check --workspace EXIT 0` + `cargo test -p df-mcp`（基础协议测试）
  5. `devflow mcp-server --print-config` 输出正确配置片段

  — 新建 `crates/df-mcp/` + `src-tauri/src/main.rs` + `src-tauri/Cargo.toml` + 可选 `crates/df-ai/src/ai_tools.rs`

---

### 💡 2026-06-19 新需求（AI 工具文件访问动态权限模型·已分析·待实施）

> 用户需求：将现有 `workspace_root` 单一根目录模型，升级为**动态白名单池 + 运行时权限申请**机制（类似 macOS / VS Code 的权限申请模式）。解决「锁太死」（只能绑定单一 workspace_root）和「放太宽」的矛盾。
>
> **与 MCP Server（F-260619-02）无关**：本需求是 DevFlow 应用内部 AI 工具（read_file/write_file 等）的文件系统访问权限升级，不涉及对外 MCP 协议暴露。

- [ ] **F-260619-03 [P1]** — **AI 工具文件访问动态权限模型：workspace_root 单根 → 动态白名单池 + 运行时申请**

  **核心机制**：当 AI 调用 `read_file` / `write_file` 等文件系统工具时，路径校验从「单一 workspace_root 前缀匹配」升级为「动态白名单池校验 + 未命中则挂起 Agentic Loop 向前端弹窗申请授权」。

  **数据结构变更**：

  1. **持久化白名单**（Settings KV 存储）：`app_settings` 表 key=`allowed_dirs`，value=JSON 数组 `["E:/wk-lab/u-abc", "E:/wk-lab/u-img"]`。前端 Settings 页提供列表增删改查 UI。
  2. **会话级临时白名单**（内存）：`AiSession` 新增 `session_allowed_dirs: HashSet<PathBuf>`，仅限当前会话有效。
  3. **全局状态**：`AppState` 引入 `AllowedDirs { persistent: HashSet<PathBuf>, session: HashSet<PathBuf> }`，替代写死的 `workspace_root()`。

  **权限拦截与申请流程**：
  1. **规范化路径**：`canonicalize`（解析软链接、`.` 和 `..`）。
  2. **检查白名单**：判断 `canonicalize` 后的真实路径是否在持久化或会话白名单中（`real_path.starts_with(allowed_dir)`）。
  3. **命中则放行**：执行原逻辑。
  4. **未命中则拦截**：挂起当前 Agentic Loop → Tauri 事件向前端弹窗 `AiDirAuthRequired { path, tool_name }` → 用户选择「仅本次」（加入 session_allowed_dirs）/「未来都允许」（写入 DB Settings + 更新 persistent）/「拒绝」→ 恢复执行。

  **授权粒度**：弹窗中建议授权目标文件所在的**项目根目录**（而非具体文件），减少弹窗频率。用户可在弹窗中手动收窄或放宽范围。

  **安全防护（三层）**：
  - **第一层 canonicalize**：解析软链接后校验真实路径，防软链接逃逸（授权目录本身也需 canonicalize）。
  - **第二层白名单**：`real_path.starts_with(allowed_dir)` 校验。
  - **第三层黑名单兜底**：保留现有 `validate_path`，禁止系统敏感目录（`/etc`、`/var`、`C:\Windows\System32` 等）。

  **写操作额外约束**：写操作（write_file/delete_file/patch_file）即使目录已授权，仍走现有 RiskLevel 审批流（Medium/High 需用户确认）；`delete_file` 始终 High 风险审批，不受白名单影响。

  **分阶段实施**：

  | 阶段 | 内容 | 复杂度 | 优先级 |
  |------|------|--------|--------|
  | **Phase A** | Settings 持久化白名单 + `resolve_workspace_path` 改造为多目录校验（canonicalize + starts_with） | 低 | P1 |
  | **Phase B** | 会话级临时授权 + Agentic Loop 挂起/恢复 + 前端弹窗 UI + Tauri 事件 | 高 | P2 |
  | **Phase C** | 软链接深度防护 + 系统目录黑名单完善 + 写操作额外约束 | 中 | P2 |

  Phase A 成本最低但收益最大——立即解决「只能绑定单一 workspace_root」的限制，且为后续动态授权打好数据基础。

  **与现有架构的契合点**：
  - SettingsRepo KV 存储：已有，`allowed_dirs` 直接复用，零迁移成本。
  - RiskLevel 审批流：已有完整 tool approval 机制，动态授权可视为「路径级别的 approval」。
  - Agentic Loop：已有挂起/恢复能力（审批等待），扩展路径授权挂起是同构的。
  - Tauri 事件系统：已有 `ai-chat-event`，新增 `AiDirAuthRequired` 事件类型即可。

  **关键改动点**：
  - `tool_registry.rs`：handler 闭包需引入 `AllowedDirs` 的 `Arc` 引用（当前闭包是无状态 `Box::new(|args| ...)`），调整注册逻辑。
  - `resolve_workspace_path`（或等效路径校验函数）：从单一 workspace_root 前缀匹配 → 多目录白名单 canonicalize 校验。
  - `state.rs`：AppState 新增 `allowed_dirs` 字段。
  - `commands/ai/mod.rs`：AiSession 新增 `session_allowed_dirs` 字段。
  - `agentic.rs`：捕获 `PATH_AUTH_REQUIRED` 信号 → 挂起 → emit 事件 → 等待恢复。
  - 前端 Settings 页：新增「授权目录」管理 UI。
  - 前端 AiChat：新增路径授权弹窗组件。

  **验收标准**：
  1. Settings 页可管理持久化授权目录列表（增删改查）
  2. AI 访问授权目录内文件正常执行，无额外弹窗
  3. AI 访问授权目录外文件时弹窗申请，用户可选择「仅本次」/「未来都允许」/「拒绝」
  4. 软链接逃逸被 canonicalize 校验拦截
  5. 系统敏感目录始终被拒绝（黑名单兜底）
  6. `cargo check --workspace EXIT 0` + `vue-tsc EXIT 0`

  — `src-tauri/src/state.rs` + `src-tauri/src/commands/ai/{mod.rs,tool_registry.rs,agentic.rs}` + `src-tauri/src/commands/settings.rs` + 前端 Settings 页 + AiChat 弹窗组件

