From 5c539fe764124d86ad03a7c84be6d61bfa26f3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sat, 27 Jun 2026 22:35:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E5=89=8D=E7=AB=AF=20TS?= =?UTF-8?q?=20=E9=98=BB=E6=96=AD=20+=20G1/G2=20=E5=8D=95=E6=B5=8B=20+=20?= =?UTF-8?q?=E5=BD=92=E6=A1=A3=E5=B7=B2=E5=AE=8C=E6=88=90=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 vue-tsc 3 阻断: i18n 重复 err key 删除 + store 补 relateIdeas - G1: extract_pinned_goal 单测 10 条(mention剥离/截断/空输入/全角冒号) - G2: is_empty_tool_result 单测 11 条(JSON标记/中文/英文/非空判定) - 归档: todo.md/docs/todo.md/待决策.md/待审查.md 已完成项迁入归档 --- docs/07-项目管理/todo归档/2026-06-27.md | 368 ++++ docs/07-项目管理/待决策归档/2026-06.md | 92 + docs/07-项目管理/待审查归档/2026-06.md | 8 + docs/todo.md | 803 +-------- docs/待决策.md | 182 +- docs/待审查.md | 1752 -------------------- src-tauri/src/commands/ai/agentic/mod.rs | 64 + src-tauri/src/commands/ai/commands/chat.rs | 64 + src/i18n/en/ideas.ts | 6 - src/i18n/zh-CN/ideas.ts | 6 - src/stores/project.ts | 1 + todo.md | 30 +- 12 files changed, 615 insertions(+), 2761 deletions(-) create mode 100644 docs/07-项目管理/todo归档/2026-06-27.md diff --git a/docs/07-项目管理/todo归档/2026-06-27.md b/docs/07-项目管理/todo归档/2026-06-27.md new file mode 100644 index 0000000..0f66f7f --- /dev/null +++ b/docs/07-项目管理/todo归档/2026-06-27.md @@ -0,0 +1,368 @@ +# Todo 归档 2026-06-27 + +> 从 todo.md / docs/todo.md 迁移的已完成项 +> 归档日期: 2026-06-27 + +--- + +## 灵感模块功能完善 — P0 数据安全/正确性 + +- [x] **#1 软删除机制**:ideas 表加 `deleted_at` 列 + 软删除(对标 tasks/projects 的 `delete` + `restore` 全套模板)。当前 `delete_idea` 是硬删除,误删不可恢复。 +- [x] **#2 白名单收紧**:`settings.rs` ideas 白名单移除 `id` / `created_at`,防止主键和创建时间被篡改。(对标 B-260616-16 tasks 白名单修复) +- [x] **#4 priority 值域校验**:`create_idea` / `update_idea` 加 priority 校验(`parse::() ∈ 0..=3`,非法返 Err)。(对标 B-260615-15 tasks priority 修复) + +## 灵感模块功能完善 — P1 架构/一致性 + +- [x] **#5 有损转换修复**:`record_to_idea()` 不再硬编码 `status=Draft` / `related_ids=空`,从 IdeaRecord 读取真实值,评估时不丢失上下文。 +- [x] **#6 评分关键词拆文件**:将 `scoring.rs` 硬编码关键词拆到独立 const 文件(最小改动)。启发式是过渡方案,对抗式评估上线后按需升级为 JSON 配置。**不做 DB 表配置化**(过度工程)。 +- [x] **#7 promote 补偿改软删除**:`promote_idea` 回写失败时的补偿删除从 `purge_with_descendants`(不可恢复)改为 `soft_delete`(软删除,可恢复)。 +- [x] **#8 关联双向同步**:A 关联 B 时自动在 B 的 `related_ids` 中补入 A,解绑时同步移除,保持关联关系对称。 +- [x] **#9 创建表单补 tags 输入**:捕捉灵感表单增加 tags 输入(逗号分隔或 chip 组件),`confirmCapture` 传 tags JSON 字符串给后端。 + +## 灵感模块功能完善 — P2 前端/UX + +- [x] **#10 创建表单补全 priority/source**:捕捉模态框增加 priority 下拉(低/中/高/紧急)和 source 输入框。 +- [x] **#14 雷达图正名/替换**:将 i18n key 和 CSS class 中的 "radar" 改为 "score-bar"(或引入真正的雷达图组件如 ECharts radar)。 + +## AI 对话目标丢失 + +- [x] **G3 状态机收敛目标** ✅文档衔接:`conv_state.rs`/`mod.rs` 注释明示 pinned_goals 内容态与 ConvState 生命周期态正交,不进 enum。 +- [x] **G5 附 bug** ✅已排查(不改代码):dump DB 确认 seq1(thinking 独白)+seq4(tool_result 内容)均 system+**早期 timestamp**(旧版本遗留脏数据),非当前 compress summary 机制问题(摘要四段式正常)。最新 system(seq0 topic marker)正常。**结论:历史脏数据,数据治理项**(可选清 DB),非当前 bug。anthropic_compat thinking 隔离你在进行中(M)。 +- [x] **G1 多目标累积**:`pinned_goal: Option` → `pinned_goals: Vec`,每次发消息/编辑/强制发送提取目标追加到列表(去重),支持换目标且不丢失历史目标。`MAX_GOALS=5` 防无限膨胀。 + +## 对抗式评估接入 — 后端 IPC 接入 + +- [x] **A1 evaluate_idea 命令**:新增 IPC command,按 Provider 配置构造 `AdversarialEngine`(有 Provider → `new(provider)`,无 → `heuristic()`),调 `evaluate(idea)` 返回 `AdversarialEval`。 +- [x] **A3 评估结果持久化**:评估结果写回 `ideas` 表(scores + evaluated_by + evaluated_at),避免每次重新评估。 +- [x] **A4 类型导出**:`AdversarialEval` / `EvaluatedBy` / `Recommendation` 等类型经 IPC serde 暴露给前端(检查 `df-types` 是否需补充)— 已验证:当前通过 `df_ideas::adversarial::*` 路径直接可用,前端经 `IdeaRecord.ai_analysis` JSON 字符串消费,无需搬运到 `df-types`。 + +## 对抗式评估接入 — 前端结果展示 + +- [x] **B1 灵感详情页评估面板**:展示正方论点 / 反方论点 / 分析师综合评分,`evaluated_by` 标签区分(LLM / 启发式兜底 / 纯启发式)。 +- [x] **B2 评估触发按钮**:灵感详情页加「深度评估」按钮,调 `evaluate_idea` IPC,loading 状态 + 错误提示。 +- [x] **B3 评分维度可视化**:可行性 / 影响力 / 紧急度三维度展示(复用现有 score-bar 或引入 radar)。 + +--- + +## 🔍 2026-06-21 AI Chat 技术债审查 + +### P1 已即时修 + +- [x] ✅(主代修·chat.rs:458-461 ai_approve exec_result 包 tokio::time::timeout(60s) 对齐 ai_authorize_dir:658·cargo check EXIT 0) **TD-260621-05a** — **[P1🔴]** ai_approve 缺 60s 执行超时(用户"静默吞消息"bug F-260620 同型复发)。ai_authorize_dir 有超时 ai_approve 漏。卡死→generating 永真→前端 130s 看门狗吞消息。 +- [x] ✅(主代修·useAiConversations.ts:99 switchConversation activeConversationId 设定后加 `state.streaming = state.generatingConvs.has(id)`·vue-tsc EXIT 0) **TD-260621-06a** — **[P1🔴]** switchConversation 不复位 streaming,真并发切非生成会话残留 stop 按钮→点 stop 发错会话。 + +### P1 归路线 + +- [x] TD-260621-01 [P1·归 F-09 B 路线 per-conv state] ✅(批2-A 落地·useAiStream `_watchdogTimers` Map + `_legacyWatchdog` fallback 双路·onStreamTimeout 携 convId 仅清该 conv·2026-06-21) — **watchdog 单计时器全清误杀并发会话**。 +- [x] TD-260621-02 [P1·归 F-09 B 路线 per-conv state] ✅(批2-A 落地·pendingMaxRounds ref 挂起 convId + 守卫比对 activeConversationId·DirAuthDialog visibleDirAuths filter conversationId·2026-06-21) — **pendingDirAuth/pendingMaxRounds 全局单 ref 非 per-conv**。 +- [x] TD-260621-03 [P1·patch_file 落地债 F-260615] ✅(读改写整体移入 _patch_guard 锁内串行化防 lost update + 删 entry().or_insert() 内存泄漏 + drop 写序列后释放·2026-06-21) — **patch_file 读改写在 FILE_LOCKS 外,lost update**。 +- [x] TD-260621-04 [P1·patch_file 落地债 F-260615] ✅(read_file 三返回点返 file_hash 闭环 + 抽 compute_file_hash helper read_file/patch_file 共用消格式漂移·2026-06-21) — **expected_hash 契约破裂,指纹防并发形同虚设**。 +- [x] ✅(2026-06-24·ba811ea chat.rs executed→completed 6处:460幂等读/584 ai_approve写/634判定/668 IPC返回/885 authorize_dir日志/889 authorize_dir写 + df-storage V27 迁移 UPDATE 存量 executed→completed + v27 两测试 unify/idempotent·cargo check EXIT 0 + df-storage 78 passed) **TD-260621-05** [P1·归 SMELL-P1-6] — **审批状态字符串双轨**(已统一)。 + +### P2 精选 — 2026-06-24 核验销账 + +- [x] ✅已修(commit a7dbd50,finalize:35 `let _ =`→match Err tracing::error,对齐 audit_finalize) — audit_tool_call 吞错 +- [x] ✅已修(a7dbd50,conversation/chat finalize_pending_placeholders pub(crate)) — switch+delete retain 占位终态化 +- [x] ✅已修(TD-260621-03 06-21,_patch_guard 串行化 + 删 entry().or_insert()) — patch_file FILE_LOCKS 永不清理 +- [x] ✅已是正确态(mod.rs:772 #[allow(dead_code)]+预留注释) — PerConvState.created_at +- [x] ✅已是正确态(阶段3a 下沉 enum ApprovalKind::Risk{diff}) — PendingApproval.diff + +## 🔍 2026-06-21 查询能力缺口 + run_workflow 缺陷 + +- [x] BUG-260621-01 [P1🔴·确定修法] — **run_workflow 空 dag 反序列化失败(missing field nodes),阻塞任务工作流推进** ✅ **2026-06-22 修复** +- [x] F-260621-02 [P2] — **任务/项目/灵感列表查询维度补全(下沉后端+关键词搜索+排序分页)** ✅ **2026-06-22 实施** + +## 🔴 2026-06-19 BUG-260619-06 L0 clear 致冷启动审批丢失(已修复) + +- [x] **BUG-260619-06** — L0 clear 致冷启动审批丢失。方案 A 修复:`lib.rs:62` `clear()` → `retain(|_, a| !a.recovered)`。 + +## 🔴 2026-06-18 Agentic 最大轮次设置不生效 + +- [x] ✅(workflow wuirgcxoy) **B-260618-23 [P1]** — **Agentic 最大轮次设置不生效(设 30 仍按 10 截断)**。修复:sync 上提到 App.vue 根 onMounted。 + +## 🔧 2026-06-17 走查·tauri 打包目标收窄 + 状态映射 DRY + +- [x] ✅(主代核验·tauri.conf.json:28 `["nsis"]` 已入库) B-260617-11 [P2] — **tauri.conf.json 打包目标收窄**。 + +## 🔧 2026-06-17 走查·DeepSeek reasoning_content 实施审查 + +- [x] ✅(2026-06-20 核验) B-260617-16 [P3·可选] — **Partial(MidStream 保文)回填半截 reasoning_content 语义待评**。 + +## 🔧 2026-06-17 对话标题不更新 + +- [x] ✅(2026-06-18) **B-260617-17 [P2]** — **对话标题不更新(对话很久/刷新后仍"新对话")**。 + +## 💡 2026-06-16 新需求(UX 交互优化) + +- [x] ✅(F-15 全阶段完成) **F-260616-15** [P1] — **AI Chat 上下文管理增强:会话分段 + 手动压缩 + 智能裁剪**。 + +## P1 — 重要缺陷 + +- [x] ✅(workflow w999qdu86) **BUG-260618-11** — **[P0🔴]** `commands.rs:289` ai_approve 幂等路径 `unwrap_or_default` 吞 DB 错误。 + +## 🔴 架构坏味道全面扫描 — P0 必须修复 + +- [x] ✅(workflow w5siwnipj) **SMELL-P0-1** — **[P0🔴]** `unwrap_or_default` 吞错 **5 高危闭环**。 +- [x] ✅(workflow w2xkw4ybh) **SMELL-P0-2** — **[P0🔴]** `tool_registry.rs:363 build_ai_tool_registry` **1091 行单函数**。 +- [x] ✅(2026-06-20 核验) **SMELL-P0-3** — **[P0🔴]** `AiChat.vue` **4026 行 God 组件**。 +- [x] ✅(workflow wowdnw4ba) **SMELL-P0-4** — **[P0🔴]** `df-execute` crate **零测试**。 + +## 🔴 架构坏味道全面扫描 — P1 应该改进 + +- [x] ✅(workflow w8774xcev) **SMELL-P1-1** — **[P1🟡]** 生产代码 206 处 `unwrap()` 排查。 +- [x] ✅(workflow wuirgcxoy no-action) **SMELL-P1-2** — **[P1🟡]** IPC 层 `.map_err(err_str)?` 样板。 +- [x] ✅(workflow wuirgcxoy no-action) **SMELL-P1-3** — **[P1🟡]** Vue views try-catch-finally invoke 模式。 +- [x] ✅(workflow w8774xcev) **SMELL-P1-4** — **[P1🟡]** df-nodes 聚合点依赖 5 crate。 +- [x] ✅(workflow weckqp9mv no-action) **SMELL-P1-5** — **[P1🟡]** IPC 层职责泄漏。 +- [x] ✅(workflow weckqp9mv no-action) **SMELL-P1-7** — **[P1🟡]** bool 参数陷阱。 +- [x] ✅(workflow wowdnw4ba) **SMELL-P1-8** — **[P1🟡]** 架构文档过时 6 篇更新。 +- [x] ✅(agent crud-split 实施) **SMELL-P1-9** — **[P1🟡]** crud.rs 2212 行按表拆分。 + +## 🔴 架构坏味道全面扫描 — P2 可选优化 + +- [x] ✅(workflow wowdnw4ba no-action) **SMELL-P2-1** — run_workflow_inner 282 行+8 参数。 +- [x] ✅(workflow wowdnw4ba) **SMELL-P2-2** — 前端全局 ErrorBoundary。 +- [x] ✅(workflow w8774xcev) **SMELL-P2-3** — HashMap → 结构体。 +- [x] ✅(workflow w5siwnipj) **SMELL-P2-4** — coordinator.rs 空壳标注 roadmap。 +- [x] ✅(workflow w5siwnipj) **SMELL-P2-5** — #[allow(dead_code)] **7 处全部标注完成**。 + +## 🔴 架构坏味道全面扫描 — sweep 派生登记 + +- [x] ✅(2026-06-22 核验) **ARC-260618-01-e** — adversarial evaluate_with_llm 一致性。 +- [x] ✅(2026-06-20) SW-260618-21 [P2] — **formatRelativeZh 重命名 formatRelative**。 +- [x] ✅(2026-06-20 核验) SW-260618-22 [P2] — **useAiSend resolveLang DRY**。 + +## P2 — 不阻断缺陷/增强 + +- [x] ✅(2026-06-20 核验) B-260614-05 — **[P2→降级]** 分离窗口(detached)跨窗口状态。 + +## 🔧 2026-06-18 6 域并行走查 sweep-fix — 已修 12 处 + +- [x] ① context.rs `estimate_message` 累加 parts Text/Image.base64 token +- [x] ② anthropic_compat.rs Image 转换 clone→move +- [x] ③ scan.rs `is_pure_badge_line` to_lowercase→to_ascii_lowercase +- [x] ④ scan.rs `is_monorepo` workspaces null 误判 +- [x] ⑤ adversarial.rs MockProvider 补 reasoning_content +- [x] ⑥ audit.rs `audit_finalize` 拆 unwrap_or_default 吞错 +- [x] ⑦ commands.rs `ai_conversation_delete` 补非活跃对话 pending_approvals retain +- [x] ⑧ audit.rs 抽 PENDING_APPROVAL_PLACEHOLDER 常量 +- [x] ⑨ ToolCard.vue isToolFailure/commandOutput 下沉 computed +- [x] ⑩ AiChat.vue 清 4 处虚拟滚动残留死注释 +- [x] ⑪ Projects.vue onUnmounted 清 _toastTimer +- [x] ⑫ Knowledge.vue refConvTitle parseContext 两次→一次 + +## 🔧 2026-06-18 6 域并行走查 sweep-fix — 新 todo 24 项 + +### P1 + +- [x] ✅(workflow wexu1isx1) SW-260618-01 [P1] — **executor Ok 路径取消节点事件/状态不一致(TOCTOU 残留)**。 +- [x] ✅(workflow wexu1isx1 + 主代修) SW-260618-02 [P1] — **审批占位 tool_result 在 stop/clear/create/delete 清 pending_approvals 时未替换终态文本**。 + +### P2 + +- [x] ✅(评估暂缓) SW-260618-03 [P2] — **Anthropic 图片 url 模式静默 400**。 +- [x] ✅(workflow wexu1isx1) SW-260618-04 [P2] — **knowledge_events 通用 query 硬编码 ORDER BY created_at 崩溃**。 +- [x] ✅(评估暂缓) SW-260618-05 [P2] — **pending 占位靠内容字符串匹配**。 +- [x] ✅(主代修·SW-06) SW-260618-06 [P2] — **ToolCard 双 watch(props.tc.status) 合并**。 +- [x] ✅(主代修·SW-07) SW-260618-07 [P2] — **AiChat 双 watch(currentText) 合并**。 +- [x] ✅(主代修·SW-08) SW-260618-08 [P2] — **AuditLog.vue 全硬编码中文未 i18n**。 +- [x] ✅(主代修·SW-09) SW-260618-09 [P2] — **AiNode/AiSelfReviewNode provider 解析+构建 DRY 重复**。 +- [x] ✅(主代修·SW-10) SW-260618-10 [P2] — **reqwest Client 构建重复(OpenAI/Anthropic Provider::new)**。 + +### P3 + +- [x] ✅(评估暂缓) SW-260618-11 [P3] — **OpenAI 图片构造防御缺失**。 +- [x] ✅(评估确认) SW-260618-12 [P3] — **ai_conversations 白名单 pinned 与 archived 不对称**。 +- [x] ✅(评估暂缓) SW-260618-13 [P3] — **find_path_conflict 跨层规范化靠文档约定**。 +- [x] ✅(评估不做) SW-260618-14 [P3] — **keyring failcount sidecar 用 current_dir 跨启动不稳**。 +- [x] ✅(主代修·SW-16) SW-260618-16 [P3] — **F-05 去重审计 status 固定 completed 不透传 rejected/failed**。 +- [x] ✅(评估不做) SW-260618-17 [P3] — **ToolCardList 4 Set 跨会话不重置累积**。 +- [x] ✅(主代修·SW-19) SW-260618-19 [P3] — **combineAndTruncateLines 与 cmdOutput 合并逻辑冗余**。 +- [x] ✅(主代修·SW-20) SW-260618-20 [P3] — **Ideas 本地 parseTags 与 store 版 DRY 重复**。 +- [x] ✅(评估暂缓) SW-260618-22 [P3] — **Knowledge getCategoryCount+parseTags 模板重复求值**。 + +## 🔧 2026-06-18 模型能力维度 + +- [x] ✅(workflow wexu1isx1) **B-260618-03 [P1] 后端路由解耦 cost_tier/intelligence**。 +- [x] ✅(workflow wexu1isx1) **B-260618-04 [P2] model_probe 去瞎填 + 预设表机制存废**。 +- [x] ✅(主代修·UX-04) **UX-260618-04 [P2] 前端删 cost/intel 标签**。 +- [x] ✅(workflow wexu1isx1 + 主代修前端) **B-260618-05 [P3] CostTier::Free 死档 + 枚举清理**。 + +## 🔧 2026-06-18 AI Chat markdown 表格布局破坏 + +- [x] ✅(workflow w5siwnipj) **B-260618-06 [P1]** — **markdown 表格 display:block 破坏布局**。 + +## 🔧 2026-06-18 aichat 工具结果渲染核对 + +- [x] ✅(workflow w2drz3ppo) UX-260618-05 [P0] — **run_workflow 结果丢 execution_id**。 +- [x] ✅(workflow w2drz3ppo+主代) UX-260618-06 [P1] — **patch_file 丢 diff**。 +- [x] ✅(workflow w2drz3ppo+主代) UX-260618-07 [P1] — **delete_file 软删丢 backup_path**。 +- [x] ✅(workflow w2drz3ppo+主代) UX-260618-08 [P1] — **read_file 丢截断提示**。 +- [x] ✅(workflow w2drz3ppo+主代) UX-260618-09 [P1] — **advance_task body 裸 JSON**。 +- [x] ✅(workflow w2drz3ppo+主代) UX-260618-10 [P1] — **list_trash 缺 header 摘要**。 +- [x] ✅(workflow w2drz3ppo+主代) UX-260618-11 [P2] — **list_directory 丢 truncated**。 +- [x] ✅(workflow w2drz3ppo+主代) UX-260618-12 [P2] — **list_* body 全裸 JSON + 各工具零散字段丢失**。 + +## 🔧 2026-06-18 列表摘要 Markdown 语法字符泄露 + +- [x] ✅(主代串行) **UX-260618-13** [P2] — **列表摘要 Markdown 语法字符泄露**。 + +## 🔧 2026-06-18 用户实测·aichat bug 反馈 + +- [x] ✅(workflow wwchro468) **UX-260618-14 [P1]** — **advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。 +- [x] ✅(agent ux15-batch1 方案A) **UX-260618-15 [P1] 第一批(方案A根治N+1)** — **流式失败重试每轮独立气泡**。 +- [x] ✅(主代串行) **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**。 +- [x] ✅(2026-06-20 核验) **UX-260618-17 [P1🟡]** — **ProjectDetail.handleApprovalMulti 漏 submitting 复位(防双击破口)**。 + +## 🔧 2026-06-19 文件拆分升级 — P0 + +- [x] ✅(2026-06-20 核验已拆分) **REFACTOR-260619-01 [P0]** — **commands.rs(ai,1923 行)拆 5 模块**。 +- [x] ✅(2026-06-22 核验) **REFACTOR-260619-02** — anthropic_compat.rs 拆模块。 +- [x] ✅(2026-06-20 核验已拆分) **REFACTOR-260619-03 [P0]** — **audit.rs(959 行)拆 5 模块**。 + +## 🔧 2026-06-19 文件拆分升级 — P1 + +- [x] ✅(2026-06-20 核验已拆分·agentic.rs 已删除) **REFACTOR-260619-05 [P1]** — **agentic.rs(1231 行)抽 agentic_runtime.rs + agentic_stream.rs**。 +- [x] ✅(2026-06-20 核验·已拆) **REFACTOR-260619-06 [P1]** — **ai_node.rs(1107 行)拆 3 模块**。 +- [x] ✅(2026-06-20 核验已拆分) **REFACTOR-260619-07 [P1]** — **(已有 SMELL-P0-3)AiChat.vue(4075)拆 ConversationSidebar/ChatHeader/MessageList/ChatInput**。 +- [x] ✅(2026-06-22 核验) **REFACTOR-260619-08** — tool_registry.rs 按功能分组注册函数拆。 +- [x] ✅(2026-06-22) **REFACTOR-260619-10** — scan.rs 拆 4 模块。 +- [x] ✅(2026-06-20 核验) **(已有 SMELL-P1-9)crud.rs(2212)按表拆**。 + +## 🔧 2026-06-19 命令行黑窗修复 + GLM 1214 数据调查 + +- [x] ✅(2026-06-19) **B-260619-01 [P1]** — **执行命令行弹黑色窗口闪烁**。 +- [x] ✅(2026-06-22) **B-260619-02** — GLM 1214 tool_result 过大截断。 +- [x] ✅(2026-06-20) **CR-260620-04 [P0]** — **1214/400 messages 非法·配对错根因(compress 绕过 sanitize)+ 停用模型路由穿透**。 + +## 💡 2026-06-19 新需求(任务关联灵感) + +- [x] ✅(2026-06-20 核验已落地) **F-260619-01 [P2]** — **任务关联灵感:TaskRecord 新增 idea_id 字段**。 + +## 💡 2026-06-19 新需求(MCP Server) + +- [x] ✅(2026-06-20 核验已落地) **F-260619-02 [P2]** — **DevFlow MCP Server:对外暴露任务/项目/灵感管理能力**。 + +## 💡 2026-06-19 新需求(AI 工具文件访问动态权限模型) + +- [x] **F-260619-03 [P1]** ✅ — **AI 工具文件访问动态权限模型**(Phase A-D 全完成)。 + +## 💡 灵感模块升级 + +- [x] ✅ 批1 可信度+体验 +- [x] ✅ 批2 评估历史全栈 +- [x] ✅ 批3 IdeasPanel 统计看板 +- [x] ✅ 批3a 晋升携带 +- [x] ✅ 批3b 关联关系 schema +- [x] ✅ 批3c 关联关系前端 +- [x] ✅ P1 信号词否定前缀 +- [x] ✅ 连带修:`augmentation/resolvers.rs` 缺 `ResolverRegistry` import + +## ⚠️ 灵感升级-遗留缺陷 + +- [x] ✅(2026-06-22 核验) **related_ids 白名单** — 实际已落地。 + +## ⚠️ 预存债 — ToolResult 类型缺字段 + +- [x] ✅(2026-06-22 核验) **TD-260621-07 [P2/预存债]** — **ToolResult 类型缺 output_mode/files/counts/matches/total_files 字段**。 +- [x] ✅(workflow wf 批1) **TD-260621-GUARD** — **[P0·用户实测卡死根因]** generating 状态机前端落地。 + +## F-260620-01 跨端 AI Chat — Phase3 联调 + +- [x] ✅(2026-06-22) F-260622-01-阶段1 +- [x] ✅(2026-06-22) F-260622-01-阶段2 +- [x] ✅(2026-06-22) F-260622-01-阶段3 + +## AI Chat 跑题/抓不住重点改进 + +- [x] ✅ P0-P2 全完成 + +## 单对话内并行多轮推理 + +- [x] ✅ Phase1 已落 + +## 🟡 F-09 决策e 前端 newConversation 未跟进 + +- [x] ✅ 已实施(CR-260620-02 审查 PASS) + +## 🟡 MED-1 bind/create/update_project AI 工具路径审批执行后 reload 未兑现 + +- [x] ✅ 已实施(chat.rs ai_approve) + +## ⚪ LOW F-09 stale 注释 + 死 i18n key 清理 + +- [x] ✅ 已实施 + +## 🔍 2026-06-22 miniapp 功能走查 — 已修项 + +- [x] P1-B-260622 [P1🔴·首屏连不上·✅2026-06-23核验已修] +- [x] P1-C-260622 [P1🔴·体验阻断·✅2026-06-23 核验已修] +- [x] P1-E-260622 [P1🟡·多轮场景·✅2026-06-23] +- [x] P2-B-260622 [P2·quickfix·✅2026-06-23核验已修] +- [x] P2-C-260622 [P2·quickfix·✅2026-06-23] +- [x] P2-A-260622 [P2·defer·✅2026-06-23] +- [x] P3-A-260622 [P3·defer·✅2026-06-23] +- [x] P1-F-260622 [P1🟡·✅2026-06-23] +- [x] P1-G-260622 [P1🟡·体验·✅2026-06-23] +- [x] P2-D-260622 [P2·健壮·✅2026-06-23核验已修] +- [x] P2-E-260622 [P2·健壮·✅2026-06-23] +- [x] P3-C-260622 [P3·清理·✅2026-06-23 核验已清] +- [x] P3-D-260622 [P3·体验·✅2026-06-23] + +## ✅ 2026-06-23 miniapp 全功能审查加固 + +- [x] F1 AiConvStateChanged idle/error 终态漏 clearWatchdog → 补 clearWatchdog +- [x] F2 regenerate() 无 generating 守卫 → 加守卫 + toast +- [x] F3 flushCurrentText 启发式回填错位 → 引入 currentAssistantMsgId 按 id 精确回填 +- [x] F9 stop() 无终态兜底 + chat 页无停止按钮 +- [x] F21 连接状态裸枚举字符串 → 中文文案 +- [x] switch 加 default warn / ws.ts 二进制帧 warn / scheduleReconnect 加 MAX_RECONNECT_ATTEMPTS=20 +- [x] 删 pages/test 死页 + +## ✅ 2026-06-23 miniapp 用户需求批 + +- [x] req1 会话重命名 +- [x] req2 断网不丢消息 +- [x] req3 审批重连恢复 + +## ✅ 2026-06-23 miniapp 收尾批 + +- [x] P1-E MaxRounds 继续/停止 +- [x] P1-F 重发 + 复制 +- [x] P3-D token 用量 +- [x] P2-C 注释陈旧 +- [x] P2-A messages 内存上限 + +## ✅ 2026-06-23 miniapp polish 批 + +- [x] P2-E 审批失败回填 +- [x] P1-G Markdown 渲染 +- [x] P3-A textarea 多行 + +## ✅ 2026-06-24 aichat 可靠性修复 + +- [x] BUG-260624-01 消息重叠/堆叠根治 +- [x] BUG-260624-02 授权弹窗卡死根治 +- [x] BUG-260624-03 工具执行心跳误报根治 +- [x] BUG-260624-04 搜索工具误授权根治 +- [x] BUG-260624-05 压缩后每轮停止根治 +- [x] BUG-260625-01 grep 传单文件路径报「目录名称无效」根治 +- [x] BUG-260625-02 read_file search 模式显示「0 行」根治 + +## ✅ 2026-06-25 generating 状态机双轨收口 + 自动压缩路径残留修复 + +- [x] ✅(2026-06-25) Task#1 自动压缩成功路径残留 +- [x] ✅(2026-06-25) Task#2 双轨状态机收口 + +## 🔍 2026-06-26 灵感模块诊断待办(已完成) + +- [x] **IDEA-FIX-01 [P0🔴]** — ideas 表无软删除 +- [x] **IDEA-FIX-02 [P0🔴]** — ideas 白名单含 id/created_at +- [x] **IDEA-FIX-03 [P0🔴]** — priority 无值域校验 +- [x] **IDEA-FIX-04 [P1🟡]** — record_to_idea 有损转换 +- [x] **IDEA-FIX-05 [P1🟡]** — 评分关键词硬编码 +- [x] **IDEA-FIX-06 [P1🟡]** — promote 补偿用 purge(不可恢复) +- [x] **IDEA-FIX-07 [P1🟡]** — 关联单向 +- [x] **IDEA-FIX-08 [P1🟡]** — 创建表单无 tags 输入 +- [x] **IDEA-FIX-09 [P2🟠]** — 创建表单缺 priority/source +- [x] **IDEA-FIX-11 [P2🟠]** — 假雷达图 diff --git a/docs/07-项目管理/待决策归档/2026-06.md b/docs/07-项目管理/待决策归档/2026-06.md index c14b3f7..6aa7d8e 100644 --- a/docs/07-项目管理/待决策归档/2026-06.md +++ b/docs/07-项目管理/待决策归档/2026-06.md @@ -351,3 +351,95 @@ - **决策点**:临时本地构建 vs 有意入库? - **选项**:A 临时本地构建(提交前 revert 此行) / B 有意入库(改按平台条件配置而非硬编码单 target) - **状态**:✅ 已决(2026-06-18) — **决策:A 临时本地构建,要求高速**。`["nsis"]` 已是 Windows 单一最快安装包目标。提交前须 revert 为 `"all"`。若进一步提速可用 `["app"]`(裸 .exe 无安装包)或 `tauri build --no-bundle`。 + +### 当前队列已决项迁入(2026-06-26 ~ 2026-06-27) + +> 来源:待决策.md ✅ 2026-06-26 任务图谱推进待决策 + 🟡 各节已决项批量迁入。 + +#### ✅ DEC-260626-01 父② 知识图谱 Phase 1 多工程合并 +- **决策**:**b** — 知识图谱独立 Phase 1(**V29**,V28 已被灵感软删除占用)。多工程代码零行(grep 全空),合并无理由,多工程后续 V30+。 +- **关联**:todo 父② +- **状态**:✅ 已决(2026-06-26) + +#### ✅ DEC-260626-02 ⑤.2 #7 promote 补偿删除方式 +- **决策**:**b** — 保留 purge。补偿是内部回滚建错的 project(非用户删除),purge 干净不污染回收站。 +- **关联**:todo ⑤.2 #7(标"保留 purge 不改") +- **状态**:✅ 已决(2026-06-26) + +#### ✅ DEC-260626-03 ⑤.2 #6 评分关键词配置形式 +- **决策**:**c** — 先拆 const 到独立文件(中/英两套,最小改动)。原推荐 a 修正:启发式是过渡(scoring.rs TODO 接 LLM 语义评分),接 LLM 后按需升 a(JSON 文件)。 +- **关联**:todo ⑤.2 #6 +- **状态**:✅ 已决(2026-06-26) + +#### ✅ DEC-260626-04 ⑤.2 #9/#10 表单补全交互 +- **决策**:**a** — tags 逗号分隔输入框(对标 Knowledge.vue 先例)+ priority 下拉 + source 输入。chip 组件留 UX 升级。 +- **关联**:todo ⑤.2 #9/#10 +- **状态**:✅ 已决(2026-06-26) + +#### ✅ DEC-260626-05 BUG-260620-05 层2 授权政策 +- **决策**:**a** — 维持方案①(白名单+弹窗三档)。层1(工程内免授权,F-260619-03 方案①)已解痛点,层2 黑名单制安全风险 + 分发冲突。 +- **关联**:todo BUG-260620-05 层2(标"维持①不做") +- **状态**:✅ 已决(2026-06-26) + +#### ✅ DEC-260626-06 父③ ToolCard 跨轮合并 + 审批状态机排期 +- **决策**:**b** — 等 G1 实测 + 父② 后再做。审批是高危路径,AiChat.vue 仍在频繁改动,择稳定窗口专项。 +- **关联**:todo 父③(排父②后) +- **状态**:✅ 已决(2026-06-26) + +#### ✅ DEC-260626-07 父④ F-09 per-conv 真多会话排期 +- **决策**:**a** — 父② Phase 1 后启动。F-09 是 Phase 4 前置,大改不与父② 并行(回归交叉)。 +- **关联**:todo 父④(排父②后) +- **状态**:✅ 已决(2026-06-26) + +#### ✅ 专题-1 workspace_root 分发适配 +- **决策**:**① 先行**(去默认白名单,dev 自用 projects.path 自动授权够),**分发前定②**(去相对锚)。 +- **状态**:✅ 已决(2026-06-26,① 立即/② 分发前) + +#### ✅ 专题-2 BUG-260623-03 审批 pending 超时兜底 +- **决策**:**c 无超时**(不做超时兜底)。用户:等待审批就是等待,一直等待,超时概念多余。审批是用户主动行为,不审批自然挂起等待,无需超时强制 reject/倒计时。 +- **状态**:✅ 已决(2026-06-26,不做超时,关闭 BUG-260623-03) + +#### ✅ 专题-3 F-09 streaming/currentText per-conv +- **决策**:**暂缓**(随 B 路线多会话并发同批做,即 DEC-07 父④)。 +- **状态**:✅ 已决(2026-06-26,归父④) + +#### ✅ S-260623-01 「自托管」含义澄清 +- **决策**:**自用阶段定案(2026-06-23)** —— 用户决策:小程序当前自用(开发阶段),分发是产品终态但留后续(过渡期靠改 device_id 实现多机)。故「自托管」现阶段 = 已实现的自主执行(autoExecuteMode all 含高危),无另义。分发阶段再做配置层(MINIDEC-01)+ 配对(MINIDEC-02)。 +- **关联**:task105 / memory(设置自主执行已实现) +- **状态**:✅ 自用阶段定案(2026-06-23) + +#### ✅ B-260618-03 路由解耦 cost_tier/intelligence(用户已全局决策去掉,工程大需专项) +- **决策**:用户已全局决策去掉。自主推进(2026-06-18 workflow wexu1isx1,主代核查 cargo check --workspace EXIT 0 + cargo test df-ai 109 passed + vue-tsc EXIT 0)。title/compress 纯 weight 选模型退化点:用户核对 Settings weight 配置(同 weight 并列 max_by_key 返回最后一个)。 +- **关联**:todo B-260618-03/04/05 · UX-260618-04 前端 cost/intel 标签(依赖本项) +- **状态**:✅ 已实施(2026-06-18) + +#### ✅ SW-260618-21 死代码预留功能清理批(清理 vs 保留) +- **决策**:**b 保留+标 allow**(预留设计意图明确·清理失去未来扩展点 ROI 低;标 allow 消 warning 即可·零波及)。risk_level 删 + 预留/diff 标 allow·cargo 0 warning。 +- **关联**:批次7 transition_status 已删 / CR-22 删 risk_level / CR-23 标 allow 5 处 +- **状态**:✅ 已实施 b(2026-06-18) + +#### ✅ F-260616-09-B 多会话并发架构 B 阶段实施决策(设计完成 2026-06-19·待拍板) +- **决策**:**✅ b-1 采纳(messages per-conv,技术必然)+ c-1 保持 3 + Settings 配置 + e-1 原路径**。主代自主裁决采纳(2026-06-19)。用户授权「自主推进,能多角度确定的方案不等审批」。启动阶段2 批1。 +- **关联**:todo F-260616-09 / 设计文档 / memory aichat-arch-extensibility +- **状态**:✅ 主代自主裁决采纳(2026-06-19) + +#### ✅ F-260619-05 任务可关联灵感(产品粒度/方向决策·todo 已登记) +- **决策**:tasks 加 `idea_id REFERENCES ideas(id)`(复用 projects 模式,1对1 起步,单向,后续按需扩展)。**已实施**(2026-06-20 调研确认)。前端任务卡片展示灵感来源(可选增强)待补。 +- **关联**:todo.md F-260619-05 +- **状态**:✅ 已实施(2026-06-20) + +#### ✅ MINIDEC-260623-03 WS 重连续流策略(F10·行为差异·需拍板) +- **决策**:**已实施 a**(2026-06-23)——用户决策「断网不丢消息 + 从远端拉完整」。useAiChat syncOnConnect:ws 'connected' → 发 load_messages 拉完整历史。cargo check 0 + vue-tsc 0 + build DONE。 +- **状态**:✅ 已实施(2026-06-23) + +#### ✅ MINIDEC-260623-04 审批双源状态分裂统一(F4/F5/F6/F13·渲染源决策) +- **决策**:**已实施 a 单一渲染源**(2026-06-23)——用户决策「审批断网重连恢复卡片状态」。审批卡改从 pendingApprovals 独立面板渲染;remote_bridge 加 sync_pending 路由;useAiChat onStatus 'connected' 清 pendingApprovals + 发 sync_pending 重建;handleEvent 加同 id 去重。cargo 0 + vue-tsc 0 + build DONE。 +- **状态**:✅ 已实施(2026-06-23) + +#### ✅ MINIDEC-260623-05 会话页管理缺口(删除/重命名·需后端命令) +- **决策**:**重命名已实施 / 删除暂缓**(2026-06-23)——用户决策「小程序对齐桌面端能改会话名」。remote_bridge 加 rename_conversation 路由;useAiChat renameConversation 方法;conversations/index.vue 长按会话编辑。删除会话暂不做(桌面端管理)。cargo 0 + vue-tsc 0 + build DONE。 +- **状态**:✅ 重命名已实施 / 删除暂缓(2026-06-23) + +#### ✅ MINIDEC-260623-07 regenerate 零调用方 + 备份组件漂移(P3 收尾) +- **决策**:**随 P1-F 选 a**(2026-06-23)—— 🟡 收尾批给 regenerate 加「重发」UI 入口(对齐桌面端),故保留函数不再删;备份组件(MdView/MentionInput)保留预留注释(绕工具解析 bug,未来拆回)。 +- **状态**:✅ 随 P1-F 选 a(2026-06-23) diff --git a/docs/07-项目管理/待审查归档/2026-06.md b/docs/07-项目管理/待审查归档/2026-06.md index d3542aa..96b1519 100644 --- a/docs/07-项目管理/待审查归档/2026-06.md +++ b/docs/07-项目管理/待审查归档/2026-06.md @@ -1060,3 +1060,11 @@ - [P3] **CR-01-G** TaskDetail.vue:218 advance 失败错误走 i18n 分级(en locale 也中文) - [P3] **CR-01-H** task_advance_node.rs:297 测试改名(实际未测 CAS 失败) - [P3] **CR-01-I** i18n `taskDetail.advancing` 冗余 key 处理 + +### 当前队列已审项迁入(2026-06-27) + +## 当前队列 + +### CR-260624-01 AI 路径授权三档化(once/session/always) — ✅ 已审(PASS·单 agent + 6-agent 对抗深审 0 finding) + +- **范围**:AllowedDirs 加 `once` 层(state.rs:444)+ ai_authorize_dir decision 改 match 四分支 once/session/always(chat.rs:846)+ 执行后 clear_once(chat.rs:924)+ 前端 DirAuthDialog 加" diff --git a/docs/todo.md b/docs/todo.md index f29ef6e..ff3a28c 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -5,6 +5,7 @@ > 核对说明: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-27 归档**: 已完成 `[x]`/`✅` 项已迁至 [07-项目管理/todo归档/2026-06-27.md](./07-项目管理/todo归档/2026-06-27.md)。 --- @@ -101,21 +102,7 @@ graph TD > 详单见 [aichat-技术债审查-2026-06-21.md](./05-代码审查/aichat-技术债审查-2026-06-21.md)。0 P0 / 7 P1 / 25 P2 / 40 P3 / 已登记 8 / 新债 64。**P1#5/#6 已即时修并自验过**。 -**P1 已即时修(2)**: -- [x] ✅(主代修·chat.rs:458-461 ai_approve exec_result 包 tokio::time::timeout(60s) 对齐 ai_authorize_dir:658·cargo check EXIT 0) **TD-260621-05a** — **[P1🔴]** ai_approve 缺 60s 执行超时(用户"静默吞消息"bug F-260620 同型复发)。ai_authorize_dir 有超时 ai_approve 漏。卡死→generating 永真→前端 130s 看门狗吞消息。 -- [x] ✅(主代修·useAiConversations.ts:99 switchConversation activeConversationId 设定后加 `state.streaming = state.generatingConvs.has(id)`·vue-tsc EXIT 0) **TD-260621-06a** — **[P1🔴]** switchConversation 不复位 streaming,真并发切非生成会话残留 stop 按钮→点 stop 发错会话。 - -**P1 归路线(5 项·涉大改/行为变更/需设计·未即时修)**: - -- [x] TD-260621-01 [P1·归 F-09 B 路线 per-conv state] ✅(批2-A 落地·useAiStream `_watchdogTimers` Map + `_legacyWatchdog` fallback 双路·onStreamTimeout 携 convId 仅清该 conv·2026-06-21) — **watchdog 单计时器全清误杀并发会话**。`useAiStream.ts:30` `_streamWatchdog` 模块级单计时器无 per-conv 隔离;`onStreamTimeout` 全清 generatingConvs+queue+currentText。F-09 真并发下 A 超时连带杀 B + 静默丢用户排队输入(注释称"防丢失"但 clear 即丢失)。修:watchdog 改 per-conv Map,onStreamTimeout 携 convId 仅清该 conv;queue clear 改仅清当前 conv 关联。归 F-260616-09 B 路线统一改造。 -- [x] TD-260621-02 [P1·归 F-09 B 路线 per-conv state] ✅(批2-A 落地·pendingMaxRounds ref 挂起 convId + 守卫比对 activeConversationId·DirAuthDialog visibleDirAuths filter conversationId·2026-06-21;🟡D pendingDirAuths 全清→per-conv filter 由 wqnd4axf8 补) — **pendingDirAuth/pendingMaxRounds 全局单 ref 非 per-conv**。`useAiEvents.ts:99/113` 模块级单实例;DirAuthDialog:60/MaxRoundsCard:54 守卫仅判 isViewingGenerating 未比对 conversationId(pendingMaxRounds 纯 boolean 无 convId 字段)。真并发下挂起弹窗错显于非挂起会话。修:pendingMaxRounds 改 ref(convId)+ DirAuth 守卫加 convId 比对。归 F-09 B 路线。 -- [x] TD-260621-03 [P1·patch_file 落地债 F-260615] ✅(读改写整体移入 _patch_guard 锁内串行化防 lost update + 删 entry().or_insert() 内存泄漏 + drop 写序列后释放·2026-06-21) — **patch_file 读改写在 FILE_LOCKS 外,lost update**。`tool_registry.rs:1247` 指纹校验(1176)+old_text CAS(1209)+new_content 计算(1197-1240)全在无锁纯读段;FILE_LOCKS(1242-1269)仅序列化 backup→tmp→rename 不防读写交错。两并发 patch 同文件:B 用基于旧内容算的 new_content 覆盖 A。write_file 不取 FILE_LOCKS 同根因。修:读+校验+new_content 整体移入锁内,或 per-path Mutex。 -- [x] TD-260621-04 [P1·patch_file 落地债 F-260615] ✅(read_file 三返回点返 file_hash 闭环 + 抽 compute_file_hash helper read_file/patch_file 共用消格式漂移·2026-06-21) — **expected_hash 契约破裂,指纹防并发形同虚设**。`tool_registry.rs:1112` schema 描述"read_file 返回的 file_hash"但 read_file handler(892-976)从不返回该字段。expected_hash 实由 patch_file 内部 format!("{}_{}",modified_secs,len)(1180)即造即比,LLM 拿不到前置值只能瞎传/省略→常 mismatch bail 或被跳过。L3 防行号漂移设计在实际 LLM 调用链落空。修:read_file 返回 file_hash 同格式闭环回传。 -- [x] ✅(2026-06-24·ba811ea chat.rs executed→completed 6处:460幂等读/584 ai_approve写/634判定/668 IPC返回/885 authorize_dir日志/889 authorize_dir写 + df-storage V27 迁移 UPDATE 存量 executed→completed + v27 两测试 unify/idempotent·cargo check EXIT 0 + df-storage 78 passed) **TD-260621-05** [P1·归 SMELL-P1-6] — **审批状态字符串双轨**(已统一)。核验全绿:前端无硬编码 'executed'(grep 全 src 零命中,AuditLog:84 是 executed_at 字段名/settings:120 是文案)+ 无 WHERE status='completed'/'executed' 统计查询(仅 pending 查询)+ find_cached SW-16 已透传真实值兼容双值 + ai_authorize_dir 无第二处判定。治 AuditLog executed 记录显示错位(蓝pending标签+raw"executed")+ DTO audit/mod.rs:53 契约对齐。**注:仅统一状态值(String 仍 String),SMELL-P1-6 String→newtype/enum 强类型化整体仍待决策(独立项,本修复不涉)**。 - -**P2 精选(25 项·详单见报告)**:try_continue pending_conv_id 跨会话泄漏(agentic:1314)/ patch_file FILE_LOCKS 永不清理内存泄漏(tool_registry:1248)/ auto-compress 成功未即时 save(agentic:778)/ MidStream 保文 chunk.error 未回填 err(stream_recv:275)/ audit_tool_call `let _ =` 吞 insert 错(finalize:35)/ switch+delete retain 未配套 finalize 占位终态化(conversation:317)/ PerConvState.created_at 全死字段(mod:520)/ PendingApproval.diff 写而不读(mod:570)。 - -**[2026-06-24 核验销账]**:audit_tool_call 吞错 ✅已修(commit a7dbd50,finalize:35 `let _ =`→match Err tracing::error,对齐 audit_finalize)+ switch+delete retain 占位终态化 ✅已修(a7dbd50,conversation/chat finalize_pending_placeholders pub(crate))+ patch_file FILE_LOCKS 永不清理 ✅已修(TD-260621-03 06-21,_patch_guard 串行化 + 删 entry().or_insert())+ PerConvState.created_at ✅已是正确态(mod.rs:772 #[allow(dead_code)]+预留注释「待 upsert 接入会话创建时间」)+ PendingApproval.diff ✅已是正确态(阶段3a 下沉 enum ApprovalKind::Risk{diff} 活调用链 audit/remote_bridge/useAiEvents,误删断前端 diff 预览);剩 3 项登记不动 —— try_continue pending_conv_id 🟡语义复杂(R-PD-6 优先审批 conv_id 设计+try_continue 回退,非确定性低风险)/ MidStream chunk.error 🟡UX 结构变更(Partial 保文加 err 字段+前端渲染,非 bug)/ auto-compress save 🟡需理解 loop save 时机判真缺口。 +**[2026-06-24 核验销账]**: try_continue pending_conv_id 🟡语义复杂(R-PD-6 优先审批 conv_id 设计+try_continue 回退,非确定性低风险)/ MidStream chunk.error 🟡UX 结构变更(Partial 保文加 err 字段+前端渲染,非 bug)/ auto-compress save 🟡需理解 loop save 时机判真缺口。 **销账核对(已登记 8 复核)**:SMELL-P1-6(line221·#5 覆盖 executed/completed 双轨)/ MED-1(tool_registry:546 bind_directory)/ REFACTOR-260619-08(tool_registry:881 拆)/ REFACTOR-260619-09(context.rs:1 God 1552行)/ ARC-260619-05(agentic:318 超长)/ UX-260617-28(双监听器 INFO)。**均仍在未完成,审查复核确认,保留**。 @@ -127,10 +114,6 @@ graph TD > 用户排查任务/项目/灵感查询能力现状,登记缺口为待办/缺陷,本轮不实施代码。详单见 [查询能力补全方案-2026-06-21.md](./02-架构设计/专项设计/查询能力补全方案-2026-06-21.md)。 -- [x] BUG-260621-01 [P1🔴·确定修法] — **run_workflow 空 dag 反序列化失败(missing field nodes),阻塞任务工作流推进** ✅ **2026-06-22 修复**:DagDef.nodes/edges 各加 `#[serde(default)]`,`{}` 反序列化成空 DagDef → workflow.rs:91 `is_empty()` 进 template_for 选模板。2 单测防回归(空 `{}` + 非空零回归),df-workflow cargo test 51 passed。— `crates/df-workflow/src/dag_def.rs` + `src/views/TaskDetail.vue`(:333) - -- [x] F-260621-02 [P2] — **任务/项目/灵感列表查询维度补全(下沉后端+关键词搜索+排序分页)** ✅ **2026-06-22 实施**:三实体新增 `list_by_query` 动态 WHERE(累积式 where_clauses+params_vec 收口)+ order_by 白名单防注入 + limit/offset 钳制;命令层 `list_{tasks,projects,ideas}` 吃 `Option` 双参向后兼容(旧无参/单参路径等价全量);前端 Tasks/Ideas status/keyword 筛选下沉后端 query(Tasks.vue buildTaskQuery / Ideas.vue watch 防抖)。索引缺口已补(2026-06-22 V26 migrate_v26 idx_tasks_priority/assignee + SCHEMA 同步,commit 82059ba)+ P3 分页 UI 已落(Paginator.vue 客户端切片 + Tasks/Ideas/Projects 三视图接,commit 83cee61)。详见查询能力补全方案-2026-06-21.md。— `commands/{task,project,idea}.rs` + `crates/df-storage/src/crud/{task,project,idea}_repo.rs` + `src/api/{task,project,idea}.ts` + `src/views/{Tasks,Projects,Ideas}.vue` - --- ### 📋 编排推进总览(2026-06-18 更新) @@ -153,7 +136,7 @@ graph TD **③④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) — 本次归档。 +> **📦 已完成项归档**: [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-27.md](./07-项目管理/todo归档/2026-06-27.md) — 本次归档。 ### 🔴 2026-06-20 BUG-260620-05 F-260619-03 工程内路径误弹窗(reload_allowed_dirs 丢 workspace_root·b22e9ae 回归) @@ -174,92 +157,6 @@ graph TD --- -### ✅ 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:500` setup block_on)→ `restore_pending_approvals`(`audit/restore.rs:26`)从 DB `list_pending`(`:27`)重建 pending 到 `session.pending_approvals` 内存。 -2. **clear 必触发(冷启动必达)**:`AiChat.vue onMounted → store.startListener()` → `useAiEvents.ts emit('ai-client-ready')` → L0 回调(`lib.rs:38`)→ **`lib.rs:62` 原为 `session.pending_approvals.clear()` 无条件清空全部**(含 restore 重建)。 -3. **前端不显示**:`ai_pending_tool_calls`(`commands/chat.rs:692` 数据源 = `session.pending_approvals.values()` 内存,非 DB)→ clear 后返空。 -4. **审批报错**:`ai_approve`(`commands/chat.rs:344`)→ DB `find_by_tool_call_id`(`:358` match)查到 status=pending 但不在「已处理」白名单(`:360 executed/rejected/failed`)→ `:370 return Err「未找到挂起的审批」`。 - -**batch8 回归证据**:`git show 6ad4ec2 -- lib.rs` 确认 `session.pending_approvals.clear()`(`:62`)是 batch8 **新增**;批2 版本 L0 只 reset generating,不 clear pending。 - -**设计契约直接违反**:`commands/conversation.rs:312-315` switchConversation 用 `retain(... != Some(&conversation_id))`(`:315`)精确保留 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` 纯内存(`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**。 - -- [x] ✅(2026-06-20 核验·MidStream 保文已回填 reasoning_content 到 ChatMessage,mod.rs:1058/1116/1121·P3 观察项已处理) 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 新需求(已分析·待实施) > 功能需求,已走查定位链路 + 方案记录,待用户确认后实施。 @@ -268,26 +165,6 @@ graph TD - [ ] 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)。 @@ -305,45 +182,18 @@ graph TD - [ ] 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 行编排 + 基线测试锁定工具数防回归。**注(2026-06-20 核验):基线已升至 29(tool_registry.rs:1841 `test_build_ai_tool_registry_baseline_tool_count` 断言 `len()==29`,18 data + 10 file + 1 http;F-260619-03 Phase A 后 http_request 注册致 +1)。** -- [x] ✅(2026-06-20 核验·AiChat.vue 4075→750 行·`src/components/ai/` 子组件已拆 ConversationSidebar/MessageList/ChatInput/TopBar/DirAuthDialog/MaxRoundsCard·状态/composable 已外移·与 REFACTOR-260619-07 同件) **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(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 → 结构体(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 条件引擎**(核验2026-06-22:conditions.rs:31 ConditionEngine + executor.rs:106 已调 evaluate 求值 + executor.rs:95 `cfg!(feature="conditions-eval")` feature flag 控制,求值逻辑已实现待产品确认启用时机)。`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`(边求值点) -- [x] ✅(2026-06-22 核验·adversarial.rs:147 `self.recommendation_for(&analyst.final_assessment)` 已守卫 final_assessment/recommendation 一致性,矛盾组合经 recommendation_for 收敛·漏勾销账) **ARC-260618-01-e** — 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 返回结构) -- [x] ✅(2026-06-20·formatRelativeZh→formatRelative 重命名 6 文件:time.ts 定义+注释去「中文」限定 + ConversationSidebar/MessageList/ActiveProjectsPanel/AuditLog/Tasks 5 调用点·AiChat 已拆 750 行无引用·grep 全 src 无残留 + vue-tsc --noEmit EXIT 0) SW-260618-21 [P2] — **formatRelativeZh 重命名 formatRelative**。`src/utils/time.ts` `formatRelativeZh` 名带 Zh 后缀但实际非中文硬编码(已读 i18n.global.locale, sweep 批2 time.ts formatDate 已修 CR-260615-08 漏修),函数名误导。原缓做前提(AiChat 沉淀)已满足,本次落地。— `src/utils/time.ts`(formatRelative) + 5 调用点(ConversationSidebar/MessageList/ActiveProjectsPanel/AuditLog/Tasks) -- [x] ✅(2026-06-20 核验·resolveLang 已抽 `src/composables/ai/aiShared.ts:30-35 resolveAiLang` 共享,消除重复) 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 方案 → 待办化) @@ -358,189 +208,26 @@ graph TD - [ ] 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 — 不阻断缺陷 / 增强 - -- [x] ✅(2026-06-20 核验·reattachPanel 接线 useAiWindow.ts:98 + tauri://destroyed 监听:86 已修·localStorage df-ai-gen/df-ai-text Sprint19 有意保留) 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 包 `
`(更可控,不依赖气泡 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` 统一渲染——不推荐(卡片截断 + 富文本样式溢出/`

` 撑高)。 - -- [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:206/265/306/364/407` 5 处 InitFailed 返回点·**注(2026-06-20 核验):UX-15 已实施,5 处均改为「不 emit AiError,返回 InitFailed{retryable,error} 交调用方」,注释明示「UX-260618-15: 不再 emit AiError」,见 stream_recv.rs:120/203/262/303),前端 `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` - -- [x] ✅(2026-06-20 核验·handleApprovalMulti 已随审批逻辑迁移到 `src/components/project/ApprovalDialog.vue:91-102`·submitting try/finally 已正确对齐 handleApproval + 模板 `:25 :disabled="submitting || multiDecisions.length === 0"` 已绑·todo 行号 431-437/196-202 过时为迁移前 ProjectDetail.vue 坐标) **UX-260618-17 [P1🟡]** — **ProjectDetail.handleApprovalMulti 漏 submitting 复位(防双击破口)**(CR-260618-25 前端审查发现·Agent B 对抗核验)。原状:`ProjectDetail.vue` handleApprovalMulti 无 submitting set true/finally,模板只绑 `:disabled="multiDecisions.length === 0"` → 多选审批 IPC 进行中按钮不禁用,可重复触发。**已修**:迁移 ApprovalDialog.vue 时补 submitting 自治 + try/finally + 模板绑 submitting。— `src/components/project/ApprovalDialog.vue`(:91-102 + :25) - ### 🔧 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(超红线/最脏,本周期优先)** - -- [x] ✅(2026-06-20 核验已拆分·`commands.rs` 已删除→`src-tauri/src/commands/ai/commands/` 目录 5 模块:chat.rs(56888 字节·发送审批控制 B 域)/config.rs(5496·杂项 E)/conversation.rs(38573·会话CRUD D)/mod.rs(2058·re-export only,`pub use self::{chat,conversation,provider,config}::*` 透传)/provider.rs(15262·提供商 C)·glob 链保留:commands/mod.rs:13 注释明 `pub use self::{chat,conversation,provider,config}::*` → ai/mod.rs `pub use self::commands::*` 透传 commands::ai::*) **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` -- [x] ✅(2026-06-22 核验·types+SSE 已抽 crates/df-ai/src/anthropic_helpers.rs(5 struct+2 const+apply_anthropic_event),anthropic_compat.rs 残留 881 行 Provider+impl(Rust 硬约束 impl 不跨文件不可再拆),lib.rs:4-5 双模块注册·对齐 _compat/_helpers 同构约定(openai/model_fetch/model_probe/context 同)·漏勾销账) **REFACTOR-260619-02** — anthropic_compat.rs 拆模块。已完成(types+SSE 抽出)。 -- [x] ✅(2026-06-20 核验已拆分·`audit.rs` 已删除→`src-tauri/src/commands/ai/audit/` 目录:mod.rs(31813 字节·process_tool_calls 热路径 + 编排)/reason.rs(7796·build_approval_reason)/finalize.rs(3874·audit_tool_call+audit_finalize,mod.rs:115 `pub(crate) use finalize::{audit_finalize, audit_tool_call}`)/cache.rs(7685·find_cached_high_risk_result)/diff.rs(1686·build_write_file_diff)/restore.rs(10033·restore_pending_approvals)/idea_source.rs(13607·任务→灵感溯源)/data_change.rs(2432)/utils.rs(1486·risk_str/常量)) **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 拆分**。**2026-06-24 核验**:ToolCard 已从 1527→**428 行**,已抽 `ToolResultBody.vue` + `useToolCardHeader.ts` + `useToolCardRender.ts`(主体瘦身完成);但原计划的 `useToolFormat.ts`/`useToolApproval.ts`/`ToolCardHeader.vue`/`ToolApproval.vue` 未独立(当前 useToolCard/useToolCardHeader/useToolCardRender 三模块)。剩余:审批状态机独立(useToolApproval)+ 头部组件(ToolCardHeader)。风险:折叠态 shouldKeepOpen 共享 / 审批状态机断链(B-260616-08 回归)/ ToolCardList 批量审批联动。— `src/components/ToolCard.vue` + `ToolCardList.vue` -- [x] ✅(2026-06-20 核验已拆分·`agentic.rs` 已删除→`src-tauri/src/commands/ai/agentic/` 目录:mod.rs(83390 字节·run_agentic_loop 主 loop)/guard.rs(2894·GeneratingGuard+ContinueSnapshot 抽出对齐 A+E+F `agentic_runtime`)·stream_recv.rs(独立文件,StreamOutcome+stream_one_provider 对齐 B+C `agentic_stream`)) **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` -- [x] ✅(2026-06-20 核验·已拆:ai_node.rs 423 + ai_self_review_node.rs 447 + ai_helpers.rs 298,测试分块清晰) **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` -- [x] ✅(2026-06-20 核验已拆分·`src/components/ai/` 子目录已存在子组件:ConversationSidebar.vue + ChatInput.vue + MessageList.vue + TopBar.vue + DirAuthDialog.vue + MaxRoundsCard.vue·AiChat.vue 从 4075 行缩到 750 行·状态/composable 已外移,模板拆分完成) **REFACTOR-260619-07 [P1]** — **(已有 SMELL-P0-3)AiChat.vue(4075)拆 ConversationSidebar/ChatHeader/MessageList/ChatInput**。方案已定,状态/composable 已外移,拆 template+局部 script。前置:先提交工作区未提交改动(本会话 shouldRenderMsg/scroll/1214 预检等)。— `src/components/AiChat.vue` -- [x] ✅(2026-06-22 核验·tool_registry.rs:442-514 已拆 register_*_tools 系列 data/file/http/project/task/workflow/idea/trash,SMELL-P0-2 批次落地·漏勾销账) **REFACTOR-260619-08** — tool_registry.rs 按功能分组注册函数拆。已完成。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` -- [x] ✅(2026-06-22·scan.rs(实际741行)拆 scan/{mod,stack,discover,readme,sample}.rs,共享 SAMPLE_IGNORED_DIRS/truncate_chars 提 mod.rs pub(super),has_file_with_ext/read_readme_raw 跨模块降 pub(super),测试 29→29 不降,cargo check --workspace EXIT 0 外部消费方零改) **REFACTOR-260619-10** — scan.rs 拆 4 模块。已完成。 -- [x] ✅(2026-06-20 核验·`crud.rs` 已删→`crates/df-storage/src/crud/` 5 文件 mod/conversation_repo/idea_repo/message_repo/project_repo·与 SMELL-P1-9 同件) **(已有 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) - -- [x] ✅(2026-06-22·conversation.rs:65 TRUNCATE_THRESHOLD 50KB→8KB + HEAD/TAIL 各3KB + 截断提示含原字节数让 LLM 感知非全文·cargo check devflow EXIT 0 + conversation 测试 25 passed·仅作用持久化视图不污染内存真相源) **B-260619-02** — GLM 1214 tool_result 过大截断。已修(大小根因,配 CR-260620-04 配对错根因互补)。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`) - -- [x] ✅(2026-06-20·cargo df-ai+devflow EXIT 0 零 warning + vue-tsc EXIT 0·主 worktree 改,workflow worktree baseRef 故障绕过) **CR-260620-04 [P0]** — **1214/400 messages 非法·配对错根因(compress 绕过 sanitize)+ 停用模型路由穿透**。用户贴 deepseek 400「tool_calls must be followed by tool messages」/「tool must be response to tool_calls」确认 **tool_use/tool_result 配对错**(与 B-260619-02 tool_result 大小根因**互补,1214 多根因**)。**根因**:`sanitize_messages`(context.rs:212 畸形配对自愈,三档+孤儿)只在 `build_for_request` 调(主对话),**`compress_via_llm`(compress.rs:46)构造 prompt 不经它**(:70 extend active_msgs 直进)→ 压缩带毒 → 400「自动上下文压缩失败」。**修法**:① `context.rs:212 fn sanitize_messages`→`pub fn`(跨 crate);② `compress.rs` 空检查后 `let active_msgs = df_ai::context::ContextManager::sanitize_messages(active_msgs)` 治毒 + `:90` 错误带 `active_count` 诊断;③ 停用模型 `agentic/mod.rs:188,450` model_override `.any(|m|m.model_id==id)`→`==id && m.enabled`(会话内 stale override 不穿透 disabled)。**关联**:B-260619-02(tool_result 大小根因 GLM 1214,截断 50K→8-10K **仍待做** `:412`)/ F-09 newConversation(CR-260620-02 ✅)/ 用户实测 deepseek 400。**未做**:tool_result 截断(B-260619-02 大小根因,GLM 若还犯需做)+ stream_recv SSE 错误 summarize(诊断仅 compress 层,stream_recv 待补)。— `crates/df-ai/src/context.rs` + `src-tauri/src/commands/ai/compress.rs` + `src-tauri/src/commands/ai/agentic/mod.rs` - ---- - ### 🔧 2026-06-19 ToolCard 同批/跨轮 UI 合并(子代理方案·其他会话实施·P1) > 用户需求:AI 多轮 read_file(轮1×2 + 轮2×2)前端一排展开卡片("上面两个下面两个"),希望 UI 合并。后端 convert 已合并连续 tool_result(B-260619-03),前端展示层对齐。子代理分析:同批同类合并**已存在**(ToolCardList groupedToolCalls L92-110,calls>=2 显标题行+默认收起),**痛点是跨轮**(轮1/轮2 各独立 ToolCardList 实例→各成分组)。用户强调"谨慎不出错"。 @@ -558,234 +245,6 @@ graph TD --- - -### 💡 2026-06-19 新需求(任务关联灵感·已分析·待实施) - -> 用户需求:推进任务时能即时、方便地关联到灵感及灵感的对抗式评估等相关信息。当前关联链路是 Idea → promote → Project → Tasks,任务和灵感只能通过项目间接关联,无法直接追溯。 - -- [x] ✅(2026-06-20 核验已落地·migrations.rs:47 `(20, migrate_v20)` + :361 `migrate_v20` 列存在探测 ALTER + :562/:579 V9_SQL tasks 建表带 idea_id 列(新库直有,老库 V20 补)·models.rs:39 + :82 TaskRecord `pub idea_id: Option` + `#[serde(default)]` 兼容旧 JSON·crud/task_repo.rs:34 `task_from_row row.get("idea_id")` + :52 INSERT + :63 UPDATE + :83/:184/:205 SELECT 列表(list_active/find_by_id/list_deleted)全含 idea_id·测试 migrations.rs:1052 `v20_legacy_db_adds_idea_id_column` + :1078 列存在跳 ALTER 验证·对齐 F-260619-05 已标✅ 同实现) **F-260619-01 [P2]** — **任务关联灵感:TaskRecord 新增 idea_id 字段**。推进任务时即时查看关联灵感的描述、多维评分(scores)、对抗式评估(ai_analysis)等信息,辅助决策。 - - **数据模型变更**:TaskRecord 新增 `pub idea_id: Option`(关联灵感 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` + `#[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`;`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 幻觉可能导致不可逆误删。**必须在协议接入的同时做工具集安全降级**。 - -- [x] ✅(2026-06-20 核验已落地·crates/df-mcp 存在:lib.rs+protocol.rs+server.rs+tools.rs,`pub use server::run_server` 导出 ·src-tauri/src/main.rs:16 `args.iter().nth(1) == Some("mcp-server")` 子命令分支 + :28 `run_mcp_server` + :99 `df_mcp::run_server(&db_path, read_only)` 启动 ·src-tauri/Cargo.toml:36 `df-mcp = { path = "../crates/df-mcp" }` 依赖已接 ·lib.rs 注释含安全降级(High 默认拒绝/Medium 日志/--read-only/不暴露文件工具)对齐设计) **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 协议暴露。 - -- [x] **F-260619-03 [P1]** ✅ — **AI 工具文件访问动态权限模型**(Phase A 持久白名单 + Phase B 会话临时/弹窗 + Phase C 黑名单 + workspace_root 去固定根完整,CR-09 ✅ + b22e9ae reload 尊重用户 persistent) - - **2026-06-24 三档授权演进**:Phase B 弹窗从 once/always 两档 → **once/session/always 三档**(本次单次执行后清 / 当前会话切会话清 / 始终落KV)。修 once 原写 session 致"本次"实为"本会话"名实不符(确定性 bug)。AllowedDirs 加 `once` 层(state.rs)+ ai_authorize_dir decision 改 match 四分支(chat.rs)+ 执行后 clear_once + tracing 审计 + 前端 DirAuthDialog 加"当前会话"按钮 + useAiApproval/api/types.ts/i18n 联合类型扩 session。cargo check 0 + vue-tsc 0。审查见待审查 CR-260624-01。 - - **核心机制**:当 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`,仅限当前会话有效。 - 3. **全局状态**:`AppState` 引入 `AllowedDirs { persistent: HashSet, session: HashSet }`,替代写死的 `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 弹窗组件 - ---- - -### F-260619-05 任务可关联灵感(tasks ↔ ideas 关联) - -**背景**:当前 tasks 表无 idea 关联字段,任务与灵感(来源/启发)无结构化关联。projects 表已有 `idea_id REFERENCES ideas(id)`(项目关联灵感),任务侧缺失。 - -**现状**(核对 `df-storage/src/migrations.rs`): -- tasks 表(:378-391):id/project_id/title/description/status/priority/branch_name/assignee — **无 idea 关联** -- projects 表(:373):`idea_id REFERENCES ideas(id)` — 项目关联灵感(**可复用模式**) -- ideas 表(:354-365):`source TEXT`(自由文本来源) -- releases 表(:399):`task_ids JSON`(发布关联多任务,1对多 JSON 数组先例) - -**设计点(待决策)**: -1. **关联粒度**:一对一(`source_idea_id`,任务来源单个灵感)vs 一对多(`related_idea_ids` JSON 数组,借鉴 releases.task_ids) -2. **存储**:tasks 加 `idea_id` 列(迁移,复用 projects.idea_id 外键模式)vs 关联表 `task_idea_links`(多对多,灵活但复杂) -3. **方向**:单向(任务→灵感)vs 双向(灵感侧反向显示关联任务列表) -4. **AI 工具**:`update_task` 支持 idea_id(白名单加列)vs 新工具 `link_task_idea` -5. **前端**:任务卡片/详情显示关联灵感 + 选择关联(mention 复用 ChatInput @灵感) - -**推荐**:tasks 加 `idea_id TEXT REFERENCES ideas(id)`(复用 projects 模式,1对1 起步,后续按需扩展 1对多),灵感侧反向查询 `list_tasks_by_idea`。 - -**依赖**:改 `df-storage/migrations.rs` + `models.rs`(TaskRecord 加 idea_id)— 与消息级溯源 P0 改同文件冲突,**待 P0 完成后实施**。 - -**改动点**: -- `crates/df-storage/src/migrations.rs`:tasks 加 idea_id 列(新迁移 V) -- `crates/df-storage/src/models.rs`:TaskRecord 加 idea_id 字段 + from_row + insert/update SQL -- `crates/df-storage/src/crud/task_repo.rs`:白名单 + idea_id 读写 -- `src-tauri/src/commands/ai/tool_registry.rs`:update_task 支持 idea_id(或新工具) -- 前端:任务卡片/详情关联灵感展示(友好 title 非裸 id)+ 选择 - -**验收**: -1. 任务可关联灵感(创建/更新时指定 idea_id) -2. 任务卡片显示关联灵感(title 友好展示) -3. 灵感详情可查关联任务列表(若双向) -4. `cargo check --workspace EXIT 0` + `vue-tsc EXIT 0` - -**状态**:✅ 已实施(tasks.idea_id 1对1 单向,commit 4a87c55,粒度/方向已定) - ---- - -### 💡 灵感模块升级(2026-06-21,批1-3c·已实施) - -> 灵感模块可信度+体验+评估历史+统计+晋升携带+关联关系全面升级。对齐 memory [[devflow-idea-module]](df-ideas 评估闭环已通,对抗未接 LLM/晋升走前端)后续延伸。 - -**✅ 批1 可信度+体验**: -- P0 `evaluated_by` 落库(ai_analysis 关联谁评的)+评估深度标签动态(LLM/启发式/降级,直显评估来源可信度)+评估失败重试(瞬时失败可恢复)+列表排序(评分/时间,支持按评分挑高价值)+详情描述可编辑(描述可订正)+已晋升项目跳转(promoted_to → 项目直跳) - -**✅ 批2 评估历史全栈**: -- `idea_evaluations` 版本快照表(每次评估留版本可追溯)+`IdeaEvalRepo`(版本数据访问层)+`evaluate_idea` 每次 insert 历史(评估产生历史记录)+`list_idea_evaluations` IPC(暴露历史查询)+`IdeaDetail` 版本时间线(前端时间线组件渲染历次评估) - -**✅ 批3 IdeasPanel 统计看板**:总数/待评估/已立项/平均分(模块概览一眼可见) - -**✅ 批3a 晋升携带**:`ProjectDetail` 来源灵感卡片(反查 `idea_id` 显示评估结论,项目可追溯灵感来源) - -**✅ 批3b 关联关系 schema**:`ideas.related_ids` JSON 列 + `IdeaRecord` 字段(为关联 UI 打底) - -**✅ 批3c 关联关系前端**:`IdeaDetail` 关联灵感展示区 + 多选管理选择器 + `Ideas.vue update-related` handler - -**✅ P1 信号词否定前缀**:`scoring.rs count_any` 不复用/无增长等否定不计正分(修评分虚高,如"无增长"误算"增长"正分) - -**验证**:cargo check src-tauri ✅ + df-storage 62/11 tests ✅ + df-ideas 22 tests ✅ + vue-tsc 灵感模块零新错 ✅ - -**连带修**:`augmentation/resolvers.rs` 缺 `ResolverRegistry` import(预存编译阻断,顺手补) - -**状态**:✅ 批1-3c 全完成(主代独立验证 cargo + tests + vue-tsc 通过) - -### ⚠️ 灵感升级-遗留缺陷(2026-06-21·待另一会话修) - -- [x] ✅(2026-06-22 核验·settings.rs:123 ideas 白名单已含 related_ids,14 列非 13,批3c 落地时已补,关联 update_field 放行非静默失败·过时 todo 销账) **related_ids 白名单** — 原"P0 功能缺失"为过时信息,实际已落地。 -- [P2 并发·🟡部分完成] 评估历史 version 并发重复:idea.rs:288-293 evaluate_idea 已加重试兜底 + V25 (idea_id,version) 唯一约束(migrations.rs)兜底。约束+重试就位,运行时确认约束生效后销账。单用户桌面低概率,严格讲需加唯一约束或事务。批2 引入。 -- [P2 运行·未验] 批1-3c 前端运行时未实测:编译/vue-tsc 过,但运行(评估历史加载/关联管理交互/统计 computed/ProjectDetail 来源卡片反查 store.ideas)未实际跑过。需 npm run tauri dev 实测。 - ---- - ### 💡 灵感模块-来源采集/追溯(2026-06-21·待办·新功能范畴) > 继批1-3c 升级后的下一步:灵感来源录入增强 + 自动采集机制。**属新功能范畴**,需独立设计采集规则/触发/去重,下一阶段推进。 @@ -799,22 +258,6 @@ graph TD --- -### ⚠️ 预存债-非灵感模块:ToolResult 类型缺字段(用户并行开发中间态·阻断 build) - -> 用户并行开发工具结果展示时,`ToolResultBody.vue` + `composables/ai/useToolCard.ts` 引用了 `ToolResult` 类型不存在的字段(vue-tsc 7+ 错)。**非本次灵感模块改动引入**,是用户工作区并行开发中间态。不阻断 `vite dev`(dev 宽松),阻断 `npm run build`(prod 严格)。 - -- [x] ✅(2026-06-22 核验·useToolCard.ts:88-100 ToolResult 已含 output_mode/matches/files/counts/total_files 五字段(F-260621 grep 跨文件搜索)+注释说明三模式语义·ToolResultBody.vue:71-95 三模式渲染消费·useToolCard.ts:424-446 formatGrep 消费·vue-tsc --noEmit EXIT 0 无 TS 错·漏勾销账) **TD-260621-07 [P2/预存债·待用户对齐]** — **ToolResult 类型缺 output_mode/files/counts/matches/total_files 字段**。 - - **现状**:`ToolResult`(types.ts)未定义 `output_mode` / `files` / `counts` / `matches` / `total_files` 五字段,但 `ToolResultBody.vue` + `useToolCard.ts` 模板/computed 已引用 → vue-tsc 报 7+ Property does not exist - - **定位**:用户并行开发工具结果展示增强(可能是为了批1 评估深度标签/批2 历史时间线之外的工具结果多模式渲染 — output_mode 暗示 tab/列表/树等多种展示模式),属进行中的中间态 - - **修法**:① `ToolResult` 类型补齐 5 字段(对齐后端返回结构) + ② `ToolResultBody.vue` / `useToolCard.ts` 消费方与类型对齐(可能需后端 `tool_result_summary` IPC 返回结构同步扩字段) - - **不阻断 dev**:vite dev 宽松回退,vue-tsc prod build 严格;用户 dev 调试不受影响,仅 build/release 受阻 - - **待用户**:确认字段语义(output_mode 取值集合 / files 结构 / counts 计什么)后对齐,非本会话灵感模块范畴 - -- [x] ✅(workflow wf 批1·streamingGuard.ts 新增 setStreaming/forceResetStreaming guard + 14 处 state.streaming 散布赋值收敛(useAiSend 7/useAiEvents 2/useAiStream 1/useAiConversations 2/useAiWindow 2/useAiPanel 1)+ feature flag df-ai-generating-statemachine(appSettings,关时回退散布语义)+ onStreamTimeout 改走 forceResetStreaming 兜底 + scripts/verify-streaming-guard.mjs 9 单测全过·vue-tsc EXIT 0·cargo check EXIT 0) **TD-260621-GUARD** — **[P0·用户实测卡死根因]** generating 状态机前端落地(对齐 memory [[devflow-generating-statemachine]] + 专项设计文档)。后端 RAII guard(B-09)+ session_state() enum 视图(B-12)+ per_conv 化(F-09 批4)历史批次已落地,本批补前端 streaming 写收敛:state.streaming 散布在 6 文件 14 处直接赋值,任一 return 漏写/前端 JS 异常跳过复位 → streaming 永久 true 卡死输入框。收敛到 setStreaming 单一写入口(合法性观测日志 + generatingConvs 联动 + flag 灰度),onStreamTimeout 走 forceResetStreaming 兜底复位 Idle+清 currentText/queue。注:TD-260621-06a(switchConversation 重算 streaming)本批已纳入 guard(convId=id 幂等联动)。 - - **关联**:批1-3c 灵感升级无涉(独立预存债) - ---- - ### F-260620-01 跨端 AI Chat:微信小程序 ↔ Rust 云后端 ↔ DevFlow 桌面端 **背景**:微信小程序远程用 DevFlow AI Chat,桌面端与小程序双向实时同步(微信电脑+手机同时在线模式)。 @@ -827,103 +270,8 @@ graph TD - **P3** df-miniapp 小程序(前端 UI 复用 + WSS 连云)— 依赖 P1/P2 - **P4** 双向同步完善(冲突处理 + 安全 + 离线降级 + 部署)— 依赖 P1/P2/P3 -**状态**:✅ Phase2 三层全落地(2026-06-22:df-relay `2b8b30e` Hello握手+鉴权+ConnRegistry配对路由+BroadcastMessage透传 / df-tunnel `25d6565` WS客户端出站穿NAT+心跳25s+指数退避+TunnelCommand 5变体 / df-miniapp `280baea` uni-app WS连relay透传+18变体AiChatEvent镜像+useAiChat分派;双 crate 独立消息骨架不依赖 src-tauri/df-types 避跨 crate 强耦合)。📐 **Phase3 联调设计已出** [F-260622-01](../docs/02-架构设计/已编号方案/F-260622-01-跨端AIChat-Phase3联调设计-2026-06-22.md)(协议统一方案A纯透传 + AiSession桥接风险 + 4阶段路线),待实施。 - **Phase3 联调待办**(D1-D6 决策点推荐:A纯透传 / 全19变体透传 / EventBus汇聚 / switch不处理 / 强类型保留 / 桥接层R1兜底): -- [x] ✅(2026-06-22)F-260622-01-阶段1(D1=A):tunnel `CommandHandler` 收 `serde_json::Value` + `parse_payload_from_broadcast` 返回 Value(入站纯透传,cargo check + 5 测试过,D5 保留 TunnelCommand 弱校验)。**出站 `send_raw_event` 移阶段2**(无阶段1 验证场景,阶段2 EventBus 接入同步加) -- [x] ✅(2026-06-22)F-260622-01-阶段2:前置决策 ② EventBus Value(0fb5af5)+ emit 双写全接入 chat19/audit12/guard3/stream_recv2/agentic18(612de4c)+ send_raw_event(030ddb6)+ tunnel subscriber(ed8e2fc)。**后端上行闭环**(EventBus→tunnel→relay→miniapp)。剩余:relay_url/device_id Settings UI(方案A)+ token 生产级(Phase4) -- [x] ✅(2026-06-22)F-260622-01-阶段3:remote_bridge 桥接(0fb5af5)+ miniapp 4命令(8ccdce2)+ on_command 注册(ed8e2fc)。**下行闭环**(miniapp Command→tunnel on_command→remote_bridge→Tauri command) - [ ] F-260622-01-阶段4:真机联调 + 多会话并发验证矩阵(6 场景含 F-09 跨端并发) -- [x] ✅ 核验项:ai_chat_send 内部 generating guard(chat.rs:354-358 有 `is_gen → Err`);R1 仍加作双保险(桥接层前置 + 内部 guard 双层) -- [x] ✅ 核验项:ai_chat_stop AwaitingApproval(chat.rs:1469-1484 finalize_pending_placeholders + retain 清 pending + generating=false + emit AiCompleted,R3 已缓解) - ---- - -### AI Chat 跑题/抓不住重点改进(跑题修复) - -**背景**:AI Chat 对话跑题/抓不住重点。根因(深度分析):系统提示无聚焦 + 意图未接入 loop + 压缩丢重点 + 工具挤占 + 无主题检测。 -**设计**:改进方案 Plan agent 规划中(优雅/可靠/易迭代三原则) - -**修复分阶段**(每阶段独立可回退,均 ✅ 完成): -- ✅ **P0** 系统提示聚焦(prompt.rs ## 聚焦准则/## Focus 独立段,102d398) -- ✅ **P0** 意图接入 agentic loop(intent filter_tool_defs 收敛工具 29→5-10 + 三重 fallback,102d398) -- ✅ **P1** 压缩增强(compress prompt 主题锚点 + 失败兜底 extract_keyword_summary,013ce21) -- ✅ **P1** 工具结果压缩(should_summarize/extract_key_info view-only 不改持久化,013ce21) -- ✅ **P2** 主题检测(TrackedMessage.topic + 双高置信保守 + tokenize 2-gram 修复中文锚点,a2db5c7) -- ✅ 苛刻测 38(边界/对抗:全漂移/全停用词/全错误行/2KB 边界/连续主题切换)+ db 基线试验记录文档 - -**状态**:✅ P0-P2 全完成(合并 de04970 + a2db5c7 + 38 苛刻测 + 试验记录文档)。待重启 dev 实跑验证 LLM 实际响应效果(机制测过 ≠ LLM 行为改善)。 - ---- - -### 单对话内并行多轮推理(主循环 ReAct 串行 → 规划式并行) - -**背景**:单对话内并行现状评估——工具级并行(Low risk `join_all`,`audit/mod.rs:448/499`)✅ + 主/辅助 LLM 2 路并发(`per_conv permits=2`,`state.rs:121`)✅,但**主循环单链 ReAct 不支持并行多轮推理**(`run_agentic_loop` `agentic.rs:331-1206` 一轮一轮串行,无法拆子流并行)。这是单对话内并行的真正天花板,`permits=2` 是配套限流(非瓶颈)。 - -**根因**:ReAct 每轮依赖前轮结果,不产生并行多轮需求。要并行多轮需**规划式分解**,`coordinator.rs`/`conditions` 空壳未落地(SMELL-P2-4 B 路线占位 / memory [[aichat-arch-extensibility]])。 - -**方向(架构·规划放哪层——分轻/重)**: -- **轻规划融入意图层**:扩展 `intent.rs` `tool_subset_for` → `plan_hint`(工具编排/可并行组/顺序)+ `suggested_model_tier` 模态。**保持纯函数零延迟**(intent 已接入 loop,跑题 P0 `filter_tool_defs` 收敛工具 29→5-10 + 三重 fallback,102d398)。轻规划是自然延伸(意图→编排提示)。 -- **重规划独立 planning 层**:`coordinator.rs` 空壳 → 演进 planning,消费 intent + plan_hint,做任务分解/子流并行调度/条件分支(conditions)。接入 loop,有状态(intent.rs 是无状态纯函数,重规划违背其定位,不塞进去)。 -- **三层链路**:`intent`(识别,纯函数)→ `plan_hint`(意图层轻规划)→ `planning`(独立重规划/调度)→ `execution`(loop 按 plan 并行跑) - -**决策点(待定)**: -- (a) 轻规划先扩(`intent → plan_hint`,低风险,跑题 P0 基础延伸)vs 直接重规划 planning(大改,coordinator 落地) -- (b) planning 形态:`coordinator.rs` 空壳演进 vs 新建 `plan.rs` -- (c) 并行多轮语义:用户是否真需单对话内并行探索多方案(当前 ReAct 单链足够大多数场景,可能非刚需) - -**关联**:`intent.rs`(CR-08)/ 跑题 P0 intent 接入(102d398)/ `coordinator.rs` 空壳(SMELL-P2-4)/ conditions(T-260614-11)/ F-09 B 路线 / memory [[aichat-arch-extensibility]] / [[aichat-roadmap-ab-split]](B 路线) - -**状态**:🔧 Phase1 已落(2026-06-22 PLANNING_ENABLED=true 主 loop 规划开关启用 + filter_tool_defs_planned 接入点就绪 commit cf7fe86,plan_hint 函数 PLAN_HINT_ENABLED 就绪双层开关可回退)。Phase2 评估(2026-06-22 agent 核验):plan_hint 完整非空壳(4 启发式规则 plan_hint.rs:264-351,Phase1 已生效生产路径非占位),Phase2=接 planner.rs LLM Plan DAG 重写主 loop 调度(L 工作量+🔴高回归风险:主 loop 绑 F-09并发/DIRAUTH/generating状态机/断路器/压缩 6 子系统),**暂缓先验 Phase1 收益**(埋点 agentic/mod.rs:648-656 就绪跑数据)。📋 待决策(Plan 严格调度 vs hint 模式双语义未定 plan_hint.rs:20-25)。属 aichat B 路线(决策能力)范畴,P0-P2 跑题已大幅改善 ReAct 单链覆盖大多数场景。 - ---- - -### 🟡 F-09 决策e 前端 newConversation 未跟进(中断弹窗遗留·用户实测) - -**背景**:用户实测——生成中点新建对话,弹「当前正在生成,确定中断并新建?」。F-09 决策e 真并发已落地(切会话/新建不该打断旧 loop,各 conv 并行),该弹窗与并行矛盾。 - -**根因**:前端 `newConversation` 是 **A 路线单 loop 软隔离遗留**(B-260615-10 + F-260616-09 A 路线),没随决策e B 路线升级: -- `AiChat.vue:246-251 confirmNewConversation`:生成中弹「中断并新建」确认(B-260615-10 软复位,单 loop「中断当前+新建」逻辑) -- `useAiConversations.ts:78 state.generatingConvs.clear()`:A 路线 F-260616-09 清字段,清空**所有**后台生成跟踪(后台 loop 后端还在跑,前端丢失跟踪 → 侧栏不显旧 conv 生成态 / 事件路由错乱) -- 对比 `switchConversation:89-91`:决策e **已落地**(「允许生成中切换:后台继续生成,事件按 conversation_id 路由」)。**`newConversation` 漏升级**。 - -**决策e 应有行为**:新建对话 = createConversation + 切 active,**旧 conv 后台 loop 继续**(后端 loop 按 conv 存在性退出,新建不删旧 conv 不打断)。不弹中断 + 不 clear generatingConvs(后台生成态保留,侧栏显双会话生成)。 - -**修法**: -- `confirmNewConversation` 去弹窗,直接 `store.newConversation`(决策e 新建=切到新 conv,旧后台继续,非中断) -- `newConversation` 去 `generatingConvs.clear()`(后台生成继续不清);`messages/queue/pendingApprovals/currentText` 是新 conv 本地视图复位,保留 -- 核对:新建后旧 conv 后台事件路由正常(`generatingConvs` 含旧 conv,`isGenerating(旧)` 仍 true,侧栏双生成) + `AiCompleted`/`AiError` 按 conversation_id 正确收尾旧 conv - -**优先级**:🟡 P1(决策e 体验破坏:用户期望并行被中断提示打断,且 clear 致后台生成前端丢失跟踪) -**关联**:F-09 决策e(CR-07 已审 PASS 后端真并发)/ B-260615-10 软复位(待废弃)/ F-260616-09 A→B 路线 / memory [[devflow-aichat-review-pending]] -**状态**:✅ 已实施(CR-260620-02 审查 PASS,confirmNewConversation 去弹窗 + newConversation 去 clear,2026-06-20) - ---- - -### 🟡 MED-1 bind/create/update_project(Medium)AI 工具路径审批执行后 reload 未兑现(CR-260620-01 审查 MED) - -**背景**:CR-260620-01 审查发现 — bind_directory/create_project/update_project 是 RiskLevel::Medium(tool_registry.rs:483/506/536),走 pending 审批,执行在 chat.rs `ai_approve`(非 process_tool_calls 内联)。bind_dir_to_project / repo.update_field 直写 DB 绕过 project.rs IPC,批3 三处 reload(create_with_binding/update_project/relocate)覆盖不到 AI 工具绑定路径。tool_registry.rs:545-549 注释承诺"reload 兜底"但代码空头。 - -**后果**:AI 用 bind_directory/create_project/update_project 工具绑定目录后,新目录未进白名单 → 后续文件工具仍误弹窗,需手动 Reload/重启。(用户手动绑定走 IPC project.rs 已修批3;AI 工具绑定路径漏) - -**修法**(单点):chat.rs `ai_approve` 执行 Medium 工具成功后,若 tool_name ∈ {bind_directory, create_project, update_project},调 `state.reload_allowed_dirs()`。覆盖所有 Medium path 绑定工具审批执行路径。 - -**优先级**:🟡 MED(非安全,用户可手动 Reload;AI 主动 bind 场景,用户主诉 IPC 路径已解决) -**关联**:CR-260620-01 审查 MED-1 / 批3 project.rs reload(IPC 路径已修)/ agent3 问题5 -**状态**:✅ 已实施(chat.rs ai_approve 两执行点 path 工具后 reload_allowed_dirs,cargo check EXIT 0,2026-06-20) - ---- - -### ⚪ LOW F-09 stale 注释 + 死 i18n key 清理(CR-260620-02 审查 LOW) - -**背景**:CR-260620-02 审查 — F-09 去中断弹窗后,3 处子组件 stale doc 注释仍写"带二次确认"(TopBar.vue:163 / ConversationSidebar.vue:226/242)+ i18n key `confirmNewConvWhileGenerating`(zh-CN settings:120 / en:119)成死 key(0 消费方)。 - -**修法**:删死 i18n key + 改 stale 注释为"决策e 直接新建"。 -**优先级**:⚪ LOW(质量清理,可选) -**关联**:CR-260620-02 审查 LOW-1 / F-09 决策e -**状态**:✅ 已实施(删 aiChat.ts confirmNewConvWhileGenerating key zh/en + 改 TopBar/Sidebar 3 处 stale 注释,vue-tsc EXIT 0,2026-06-20) - ---- ### 🔍 2026-06-22 miniapp 功能走查(workflow wf_ddd49ac8 · 5 域并行核验 + 主代终态抽查) @@ -933,23 +281,6 @@ graph TD - [ ] P1-D-260622 [P1🔴·真机分发最高阻断] — **token/deviceId 硬编码 + 无配对绑定 + 无 storage 持久化**。`config.ts:35-39` 硬编码本机 device_id+token(注释自认「联调期硬编码,后续做配对绑定」),`:52-54` setConfig 仅内存赋值无 `uni.setStorageSync`。真机分发不能用本机硬编码 device_id。修:settings 页输入 + setConfig 持久化 storage + getConfig 读回退 default。— `apps/df-miniapp/src/config.ts` - [ ] P1-A-260622 [P1🔴·可靠性] — **心跳 pong 闭环缺失**。`ws.ts:226` 发 `{control_kind:'ping'}`,`relay.rs:355` miniapp 入站全标 `MessageKind::Command` 透传 device,但 ping 无 cmd 字段 → `remote_bridge.rs:143` match 无 ping 分支 → device 收不到有效命令无 pong 回;`useAiChat.ts:440` onControl 仅 console.log 无 pong 处理;`ws.ts:245-257` 重连仅靠 onClose/onError 无心跳超时。移动网络 TCP 半连接高发,device 静默挂死无感知。修:relay 解析 ping 构造 Control 类型 + device 响应 pong + miniapp 维护 lastPongTime 看门狗(3×interval 未收 pong 主动重连)。— `ws.ts` + `relay.rs:handle_inbound_text` + `useAiChat.ts` -- [x] P1-B-260622 [P1🔴·首屏连不上·✅2026-06-23核验已修] — **握手判定脆弱**。`ws.ts:185` `text.includes('"error"')` 字符匹配判握手失败(真机业务消息含 error 字符误判);同时 `ws.ts:181` 注释「relay 无显式 ack」错——`relay.rs:255` 实发 `{control_kind:"hello_ack"}` 但代码漏检。修:handleHandshakeResponse 改结构化解析——判 `msg.kind==='control' && msg.control_kind==='hello_ack'` 为成功,error 判 `msg.error` 字段存在性。— `apps/df-miniapp/src/api/ws.ts` -- [x] P1-C-260622 [P1🔴·体验阻断·✅2026-06-23 核验已修] — **冷启动不恢复会话历史**。`App.vue:8-10` onLaunch 仅 connect,`chat/index.vue:62-64` onShow 仅 scrollToBottom,`useAiChat.ts:61` activeConversationId 初始 null → 真机首次打开空白。修:连接成功后 refreshConversations;chat onShow 中 activeConversationId=null 且有会话时自动选最近会话 + switchConversation(load_messages)。— `App.vue` + `chat/index.vue` + `useAiChat.ts` -- [x] P1-E-260622 [P1🟡·多轮场景·✅2026-06-23] — **continueLoop/stopLoop 无 UI 入口**。`useAiChat.ts:699-700` 已导出,`useAiChat.ts:241-250` AiMaxRoundsReached 仅 clearWatchdog + 系统气泡「请去桌面端处理」,chat/index.vue 全文无调用。多轮场景用户真机无法继续/停止。修:max rounds 时 chat 渲染继续/停止按钮调 continueLoop/stopLoop。— `apps/df-miniapp/src/pages/chat/index.vue` - -**P2(UX/健壮性)**: - -- [x] P2-B-260622 [P2·quickfix·✅2026-06-23核验已修] — **deviceOnline 离线不复位**。`useAiChat.ts:76-77` 注释自认「MVP 不做超时复位」,全 src 无 `deviceOnline.value=false`,onStatus 断开/重连只 clearWatchdog。device 断线状态滞后。修:onStatus 回调 disconnected/reconnecting 加 `deviceOnline.value=false`(一行)。— `useAiChat.ts` -- [x] P2-C-260622 [P2·quickfix·✅2026-06-23] — **路由表注释陈旧(双侧)**。`types/relay.ts:86` 称「8 条」实际 match 10 条;`remote_bridge.rs:13-25` 注释列 9 条实际 10(F-#95 扩展 list_conversations/load_messages 未同步注释)。修:双侧注释补 list_conversations/load_messages + 数字改 10。— `types/relay.ts:86-88` + `remote_bridge.rs:13-25` - -**P3/defer**: - -- [x] P2-A-260622 [P2·defer·✅2026-06-23] — **messages 内存态无上限(OOM)**。`useAiChat.ts:56` reactive 无 MAX_MESSAGE。联调短会话无风险,长会话再做(MAX_MESSAGES=100 + truncate 保留最新 50)。— **已修**:watch trim MAX_STORE_MESSAGES=500(渲染另有 visibleMessages slice 200 兜底)。 -- [x] P3-A-260622 [P3·defer·✅2026-06-23] — **输入框单行非 textarea**。`chat/index.vue:176-184` ``。MVP 单行可接受,多行场景再升级 textarea auto-height。— **已改 input→textarea auto-height**(max-height 120px + flex-end 对齐;⚠️ 原生层级遮联想浮层风险待用户实测,fallback cover-view)。 - -**已驳回(refuted·不记 todo)**:regenerate 双 user 气泡(UI 展示层非逻辑错,switchConversation 即清)/ 看门狗 130s(实现正确无需修)/ 审批卡+流式光标(完成项核对,非问题)。 - -**真机分发前置序(P1 按依赖)**:P1-D(token/配对)→ P1-A(心跳闭环)→ P1-B(握手解析)→ P1-C(冷启动恢复)→ P1-E(max UI)。P2-B/C 可同批 quickfix(注释对齐 + 一行复位)。 **用户补充核验(2026-06-22 用户详读源码+设计文档,主代逐项核源码判真伪)**: @@ -958,143 +289,19 @@ graph TD > - **P0-2 EventBus**:`src-tauri/src/lib.rs:128` `state.ai_event_bus.subscribe()`(tunnel subscriber 已接)+ `remote_bridge.rs:303/332/358/388` + `commands/ai/mod.rs:209/225` `publish_event` 跨端透传。**已双写接入**(todo line 760 commit 0fb5af5/612de4c emit 双写 chat19/audit12/guard3/stream_recv2/agentic18)。`event_bus.rs:53/115` 的 `#![allow(dead_code)]`+「骨架阶段」注释是**过时历史标注**(publish_event 现有调用方 remote_bridge/mod),非未接入。 > - **P0-3 桥接层**:`remote_bridge.rs` 完整实装(`handle_remote_command`:121 + `MiniCommand`:83 + `from_payload`:96 + match 路由 10 条 + `route_list/load_messages` publish_event)+ **已 wire** `lib.rs:150-154` `on_command = Arc::new(...handle_remote_command...)` + `:171` `.connect(&relay_url,&device_id,&token,on_command.clone())`。非占位/设计态。`remote_bridge.rs:40-42` 的 `#![allow(dead_code)]`+「零调用方」注释写于联调前,现 lib.rs 已调(过时标注,待清)。 -**属实补充项(用户提出 + 主代核源码确认,追加 todo)**: - -- [x] P1-F-260622 [P1🟡·✅2026-06-23] — **消息操作缺失(复制 / regenerate UI 入口)**。`regenerate()`(`useAiChat.ts:520`)已实现但 chat 页无入口;复制消息功能全缺(桌面端有 hover 操作栏)。修:消息气泡加长按/点击操作(复制全文 / regenerate)。— `apps/df-miniapp/src/pages/chat/index.vue` -- [x] P1-G-260622 [P1🟡·体验·✅2026-06-23] — **Markdown 渲染简陋**。`MdView.vue` 用 rich-text 无代码高亮 / 图片预览 / 链接拦截(MdView.vue 注释自认弱点);流式无节流(注释承认「长回复卡顿后续 P1 加 setTimeout 节流」)。修:评估 mp-html CLI local import `` 子组件不渲染问题的修复,或换方案 + 流式 setTimeout 节流。— `apps/df-miniapp/src/components/MdView.vue` -- [x] P2-D-260622 [P2·健壮·✅2026-06-23核验已修] — **断连期间操作静默丢失**。`wsClient.send()` 失败仅 console.warn + 返 false,但 `stop()/approve()/authorizeDir()` 忽略返回值 → 用户点「批准」UI 已乐观移除 pending 但命令未发出。修:这些方法检查 send 返回值,失败 toast 提示 + 不移除 pending。— `apps/df-miniapp/src/composables/useAiChat.ts` -- [x] P2-E-260622 [P2·健壮·✅2026-06-23] — **审批乐观移除无失败回退**。approve() 即 splice pending,若 device 端 tool_call_id 已被桌面端处理致审批失败,miniapp 已移除 UI 误导用户以为成功。修:approve 失败(超时 / AiError 事件)回填 pending。— `useAiChat.ts` -- [ ] P3-B-260622 [P3·真机前置] — **manifest appid 占位**。`PLACEHOLDER` 无法真机调试,需微信公众平台注册的真 appid。— `apps/df-miniapp/src/manifest.json` -- [x] P3-C-260622 [P3·清理·✅2026-06-23 核验已清] — **mp-html 死依赖残留**。package.json 仍引 `mp-html@^2.5.2` 但 MdView 已改 rich-text 弃用。确认不回退 mp-html 则移除依赖(当前 `src/components/mp-html/` 预留保留,依 [[dead-code-reserve-keep]] 待回退决策)。— `apps/df-miniapp/package.json` — **核验:package.json 已无 mp-html + grep 全 src 零引用,已清(陈旧 todo)**。 -- [x] P3-D-260622 [P3·体验·✅2026-06-23] — **token 用量 UI 缺失**。`tokenUsage` 状态有但页面未展示。— `apps/df-miniapp/src/pages/chat/index.vue` - -### ✅ 2026-06-23 miniapp 全功能审查加固(workflow wll7qabgr + 主代串行) - -> 6 区域并行审查(chat/会话页/useAiChat/连接层/组件/工程;2 区域限流主代补审)。**自主实施**(逐批 vue-tsc 0 + build DONE 核验): -> - **F1** `AiConvStateChanged` idle/error 终态漏 clearWatchdog → 补 clearWatchdog(兑现注释承诺,防 timer 泄漏假超时) -> - **F2** regenerate() 无 generating 守卫 → 加守卫 + toast(防 send 静默 return 无反馈) -> - **F3** flushCurrentText 启发式回填错位 → 引入 currentAssistantMsgId 按 id 精确回填(治 retry/error/compressed 气泡穿插污染) -> - **F9** stop() 无终态兜底 + **chat 页无停止按钮** → stopping 标志乐观复位 + 忽略迟到 delta + 生成中显「停止」按钮(原 generating 时 send 禁用且无停止入口,用户无法中断生成) -> - **F21** 连接状态裸枚举字符串 → 中文文案 + 断开/重连中点状态栏手动重连 -> - switch 加 default warn / ws.ts 二进制帧 warn / scheduleReconnect 加 MAX_RECONNECT_ATTEMPTS=20 + 手动重连复位计数 -> - 删 pages/test 死页(uniapp alpha tap 排查探针,grep 零引用) -> -> **06-22 走查项核验销账**(源码已修,标 [x]):P2-B deviceOnline 复位 ✅ / P2-D 断连操作 send 返回值检查 ✅ / P1-B 握手结构化解析 ✅ -> -> **06-22 项部分缓解**(alternative approach):P1-A 心跳 pong 闭环 → 当前用「任意入站消息」活性检测(HEARTBEAT_DEAD_FACTOR)替代 pong 往返,半开死连接已有兜底;pong 精确闭环见 MINIDEC-260623-06 ⏸️ / P1-C 冷启动恢复 → active conv id 已持久化 + 握手发 list_conversations;冷启动自动 load_messages 待核 -> -> **剩余开放项 → 待决策**(见 [待决策.md](./待决策.md) df-miniapp 组):MINIDEC-260623-01 配置层(=旧 P1-D)/ 02 配对 / 06 心跳历史协议 / 07 regenerate+备份组件漂移。(03 重连续流 / 04 审批双源 / 05 会话页重命名 已实施,见下)。06-22 走查剩余 [ ]:P1-A(缓解)/P1-E(MaxRounds UI)/P1-F(复制+regenerate UI)/P1-G(Markdown 渲染)/P2-C(注释)/P2-E(审批回填)/P3-A/B/C/D。 - -### ✅ 2026-06-23 miniapp 用户需求批(req1/2/3 · 用户决策后实施) - -> 用户 3 要求:(1) 会话重命名对齐桌面端 (2) 断网消息不丢失+从远端拉完整 (3) 审批断网重连恢复卡片状态。逐批 cargo check 0 + vue-tsc 0 + build DONE 核验。 -> - **req1 会话重命名**:remote_bridge `rename_conversation` 路由(调 ai_conversation_rename + 推 AiConversationList 刷新)+ useAiChat `renameConversation`(乐观本地更新)+ conversations/index.vue 长按 `uni.showModal` 编辑。 -> - **req2 断网不丢消息**:useAiChat `syncOnConnect` —— ws 'connected' 触发发 load_messages 拉当前会话完整历史(断网 missed 消息恢复 + 兼修冷启动空白 P1-C-260622)。 -> - **req3 审批重连恢复**:remote_bridge `sync_pending` 路由(读 AiSession.pending_approvals 按 conv 过滤,重发 AiApprovalRequired/AiDirAuthRequired)+ useAiChat onStatus 'connected' 清 pendingApprovals + 发 sync_pending 重建 + handleEvent 同 id 去重防竞态。**审批卡改从 pendingApprovals 独立面板渲染**(与 messages 解耦,避重连 load_messages 替换 messages 的竞态;落地 MINIDEC-04 单一渲染源决策 a),内联工具卡保留状态徽标/参数/结果。 -> - **P1-C-260622 冷启动恢复** 顺带修复(syncOnConnect 初始连接也拉历史 + active conv id 已持久化)。 - -### ✅ 2026-06-23 miniapp 🟡 收尾批(P1-E/F + P2-C/A + P3-D/C · 自主推进) - -> 用户定「自用」后低风险收尾项一次性做完。逐项 vue-tsc 0 + build DONE 核验。 -> - **P1-E MaxRounds 继续/停止**:useAiChat `maxRoundsActive` ref(AiMaxRoundsReached 置 true,continueLoop/stopLoop/send/switch/new 清)+ chat 「已达最大轮次」面板(继续一轮/停止按钮)。 -> - **P1-F 重发 + 复制**:chat 输入栏「重发」按钮(调 regenerate,非生成中且有消息时显)+ 消息 @longpress 复制全文(uni.setClipboardData)。 -> - **P3-D token 用量**:顶栏显 🪙 total(ai.tokenUsage)。 -> - **P2-C 注释陈旧**:relay.ts 路由表 12→14(+ rename/sync_pending)+ remote_bridge.rs 表头补两行。 -> - **P2-A messages 内存上限**:useAiChat watch trim MAX_STORE_MESSAGES=500(渲染另有 visibleMessages slice 200)。 -> - **P3-C mp-html 死依赖**:核验 package.json 已无 + grep 全 src 零引用(陈旧 todo,无需改)。 -> - **MINIDEC-07** 随 P1-F 选 a(regenerate 加 UI 不删)+ 备份组件保留预留,已落。 -> -> **剩余 [ ](非阻塞)**:P1-A 心跳 pong(已缓解=MINIDEC-06 暂缓)/ P3-B manifest appid(真机前置,需注册)。(P1-G/P2-E/P3-A 见下 polish 批已落) - -### ✅ 2026-06-23 miniapp polish 批(workflow wll377z2c 设计 → 串行实施) - -> workflow 3 agent 并行设计(无限流)→ 汇总排序 → 主代串行实施,逐项 vue-tsc 0 + build DONE。 -> - **P2-E 审批失败回填**(low):useAiChat `lastApprovedTc` ref —— approve(approved=true) 记录,device 返 AiError 回填 pendingApprovals + toast「审批操作未生效,请重试」让用户重试;AiApprovalResult/AiToolCallCompleted 确认 → 清;切会话/新建/断连 → 清。 -> - **P1-G Markdown 渲染**(medium):新建 `utils/mdRenderer.ts` —— marked HTML 后处理注入 inline style(pre 深底等宽/code 橙色/a 蓝下划线/table border),规避 marked renderer v18 签名风险(rich-text 不认 class)。chat/index.vue renderMd + MdView.vue 接入。**链接跳转 rich-text 不可行**( 节点事件屏蔽)降级展示+长按复制,跳转留 mp-html 后续。 -> - **P3-A textarea 多行**(high):input→textarea auto-height(max-height 120px + .bar flex-end 对齐)。⚠️ **原生层级遮联想浮层风险待用户实测**(textarea 原生组件渲染在 WebView 之上,可能遮盖 .mention-pop);若遮挡严重,fallback 联想浮层改 cover-view(单独任务,本次未做)。 -> -> **miniapp 自用阶段功能全部闭环**。剩 P1-A(暂缓)/P3-B(appid 真机前置,需你注册)。 - -**与主走查重复(用户项 = 已登记,跳过)**:用户 P1-5(配置硬编码)= P1-D / P1-6(MaxRounds UI)= P1-E / P2-11(deviceOnline 不复位)= P2-B / P2-9(R1 并发防护)= `remote_bridge.rs:27-33` `check_generating_reject` **已实现**(send_message 路由前置 generating 检查)。 - ---- +**剩余开放项**:P3-B-260622 [P3·真机前置] — **manifest appid 占位**。`PLACEHOLDER` 无法真机调试,需微信公众平台注册的真 appid。— `apps/df-miniapp/src/manifest.json` ### ✅ 2026-06-24 aichat 可靠性修复(消息重叠×5 + 授权弹窗卡死 + 工具执行心跳误报 · 诊断workflow→论证workflow) -> 用户「从历史消息找需求逐一比对」→ 捞出反复报告未根治的可靠性 bug。**诊断 workflow(4 agent 只读核验)** 定根因 → **论证 workflow(4 角度:正确性/回归/完整性/边界)** 验正确性+完整性 → 补 2 处(完整性高优+回归中优)。vue-tsc 0。 -> -> **BUG-260624-01 消息重叠/堆叠根治**(用户 2d89921a×3/d056b5ad×2 共 5 次报告): -> - 根因:currentText 全局单例 + flushCurrentText 末尾盲写 + MessageList streamingBlocks(:69 实例级)跨轮/跨会话残留 + 渲染 isLastAi&¤tText 非 streaming 态也驱动。 -> - 修复(4 处协同,治本非补丁):① useAiEvents `flushCurrentText` 回填后自清 currentText(消除调用方清空顺序依赖)② MessageList currentText watch 归零清 streamingBlocks+lastStreamText(新轮)③ 1011 v-if 加 streaming(堵非 streaming 残留)④ activeConversationId watch 切会话清 streamingBlocks+lastStreamText+cancelAnimationFrame(治 switchConversation/restoreGeneratingState 残留根因,论证完整性高优)。 -> -> **BUG-260624-02 授权弹窗卡死根治**(用户 8e4944b7/b1f644a7「没审批直接死了」): -> - 根因:统一审批开关 `df-ai-unified-approval` 默认开 → path 审批走 ToolCard 内联,但同名工具≥2 分组默认折叠(group-hidden display:none)→ 审批卡不可见 → 5min 超时(APPROVAL_TIMEOUT_MS)静默 deny → 误显「用户拒绝」卡死。ToolCard 根 :5 动态拼 `ai-tool-card--'+tc.status` 已挂 pending_approval class(scrollToFirstPending 可命中)。 -> - 修复:useAiEvents AiDirAuthRequired/AiApprovalRequired case 内 `emit('ai-pending-arrived')`;ToolCardList onMounted listen → scrollToFirstPending(自动展开折叠组+scroll,原仅手动徽标触发)+ 卸载竞态 disposed 标志(论证回归中优)。 -> -> **诊断推翻的误判**(anti-contamination 印证):压缩「未降prompt」= 指标口径(accumulate_tokens 跨轮累计非单轮;压缩真省,is_active 白名单剔除 compressed)✅ / generating 状态机 = 已落地(RAII guard guard.rs + enum 视图 session_state + per_conv,原创建对话硬拦已删)✅。task #120 关闭。 -> -> **BUG-260624-03 工具执行心跳误报根治**(用户 b1f644a7「一边 aichat 回复着内容,一边抛"工具已执行完成,后续回复中断"」): -> - 根因(论证 workflow 4 维度·7 条源码证据闭环):前端 watchdog 130s 无活跃事件→判断断流→抛错;后端 AiHeartbeat 30s 静默期 emit 让前端 reset,但**心跳只在 `stream_llm`(provider 读 chunk 的 select! 循环,stream_recv.rs:333)内,工具执行在 audit/mod.rs(stream_llm 外)无心跳**。单次工具执行>130s(bash 跑 cargo/测试、read 大文件、全盘 search)→ watchdog 到期→该 conv 仍 generatingConvs→守卫放行→前轮有 completed 工具即命中"工具已执行完成后续中断"文案;前一轮 delta 文本在屏 = "一边流一边抛"。**反复修未根治因都在前端守卫打补丁(B-260615-03 文案/BUG-260623-06 MaxRounds clear/TD-260621-01 残留 timer),没人补工具执行阶段的心跳缺口**。 -> - 修复(A 根治 + B 兜底,论证质量 A 可发布): -> - **A 后端根治**(audit/mod.rs):新增 `execute_with_heartbeat` helper(execute 期间 30s interval emit AiHeartbeat,弃首 tick 对齐 stream_recv:174;**RAII HeartbeatGuard** drop 自动 stop+abort,panic unwind 也不泄漏 task),替换 trust_hits/low_risk 两处 `tools.execute()`。补工具执行阶段心跳缺口。 -> - **B 前端兜底**(useAiStream onStreamTimeout):stillGenerating 守卫后扫 messages running 工具卡→resetStreamWatchdog 续等不抛错(防心跳丢失/老后端/审批后路径误杀)。审批路径(chat.rs:575/877)非盲区——已包 60s timeout<130s watchdog 互补治理(论证勘误维度3 误判)。 -> - 验证:cargo check exit 0(RAII 编译过)+ vue-tsc 0。B 维度因网络错未跑,已自核验 ToolCard approving 计时器仅复位按钮 loading 非 running(running 由后端 AiToolCallCompleted 驱动),真死锁续等优于误报(用户可 stop),A 心跳覆盖正常执行。 -> -> **BUG-260624-04 搜索工具误授权根治**(用户 b1f644a7「搜索代码也要授权,检查这样的会话搜索为什么需要授权」): -> - 根因:`grep` 工具被标 `RiskLevel::Medium`(tool_registry.rs:1682),而同类只读 `read_file`/`search_files`/`read_symbol` 均 Low。注释 line 1661 "risk:Med(读文件内容,授权目录内放行/外申请)" 把"读内容需 path_auth 授权"误塞 risk_level —— path_auth 是独立安全层(read_file Low 也走),与 risk_level(只读/写/删)混淆。默认 low 模式下 Medium 需审批 → 每次搜索弹审批。叠加 `tool_display_hint`(tool_registry:2116)无 grep 条目 → reason.rs:158 fallback default `"创建操作,请确认是否执行"` → 只读搜索显示"创建操作"误导。 -> - 修复:`grep` Medium→**Low**(对齐只读同类)+ tool_display_hint 加 `"grep" => Some("搜索代码内容")`(文案准确)+ 注释更正。cargo check exit 0。 -> - 全量核对:28 工具 risk_level 分级,**除 `restore_project`: High 过保守(恢复非破坏,对齐 create/update 该 Medium;但 High 安全方向不算错,未改)** 外全合理。 -> -> **BUG-260624-05 压缩后每轮停止根治**(用户「上下文压缩后,后续所有对话都提示已压缩然后停止」): -> - 根因(诊断 workflow 4 维度·DB 铁证 top.1216.devflow 库 565 compressed/4246 active,ead0f7e5 seq=0 是 active 兜底摘要,12 会话前7条有 active system): -> - **#1(主因·无限循环)**:压缩摘要 `insert_at(0, ChatMessage::system(summary))`(agentic/mod.rs:923)→ status=None → is_active=true,落在可压缩区 [0..protect_start) → 每轮 `has_compressible_messages` 恒 true → 无限循环压缩。topic-switch system(DB 3eafdb85 三条堆 seq 0/1/2)同理。 -> - **#2(次因·前端停止)**:压缩失败 emit AiError(useAiEvents:648 无条件 setStreaming(false))→ 前端误判生成结束(后端 loop 实际继续)。 -> - 修复(P0 根治循环 + P1 前端停止): -> - **P0**(context.rs:803 `has_compressible_messages` 排除 system 角色,用 `!matches!(role, System)` 因 MessageRole 无 derive PartialEq):摘要/话题锚点是上下文锚点非压缩目标,排除后不再因 system 触发。compress_old_messages 不改(被调用时仍标旧 system 摘要 compressed 被新摘要替代,防堆积)。DB 自愈:排除 system 不限 status,旧 active 摘要也被排除,**无需 migration**。 -> - **P1**(agentic/mod.rs:985 压缩失败不发 AiError):前端 AiError 无条件停流,降级 warn + build_for_request 裁剪兜底,loop 继续 stream_llm。 -> - **纠正 workflow 方案 A 矛盾**:workflow 建议 is_active 排除 summarized,但会致 build_for_request 不送摘要进 LLM 丢历史;改用 has_compressible 排除 system(摘要仍 active 进 LLM)。 -> -> **BUG-260625-01 grep 传单文件路径报「目录名称无效」根治**(用户「grep path 需要传目录不能传单文件·怎么总是失败」): -> - 根因(DB 铁证 ai_tool_executions fb549a0b):grep handler 直接 `grep_recursive(root)` → 内部 `read_dir(root)`,root 是单文件时报 os error 267「目录名称无效」。grep 命令行原生支持单文件,LLM 常传文件路径(64 次 grep 调用多次传文件),工具只认目录 →「总是失败」。 -> - 修复(tool_registry.rs):提取 `grep_one_file`(从 grep_recursive 循环体搬移,continue→return Ok),grep_recursive 改调它(DRY 复用),handler 判 `root_meta.is_file()` → 单文件走 grep_one_file,is_dir → 递归。描述加「path 文件或目录均可」。 -> - 验证:cargo test grep **13 passed**(9 个 grep_recursive 测试全过 = 提取零回归)+ audit 3 grep 测试。 -> -> **BUG-260625-02 read_file search 模式显示「0 行」根治**(用户「读取 MessageList.vue 0 行·65.2KB·怎么总是失败」): -> - 根因(DB 铁证 ai_tool_executions cad747e9):read_file 带 `search` 参数返 `{matches:[{line,content}],total,has_more}` **无 content/lines/returned_lines**。ToolResultBody.vue read_file 分支只认 content/lines → 显示「0 行 · 65.2KB」+ 空 content 区。LLM 拿到 matches 能继续,但**用户看 UI 以为读取失败/空**。 -> - 修复(ToolResultBody.vue + useToolCard.ts):read_file 分支识别 search 模式(parsed.search),header 显「『search』 命中 N 处」(复用 grepHitsN)+ 命中行渲染(复用 grep content 样式,read_file matches 仅 {line,content} 无 file);ToolResult 加 `search?: string` 字段。 -> - 验证:vue-tsc EXIT 0。 -> - 附:state.rs/tool_registry.rs test 的 AllowedDirs once 字段初始化是工作区被改掉的预存状态,本次恢复到 HEAD(once 功能早已提交),**未引入新改动**。 -> -> **剩余低优 [ ](非阻断)**: -> - [x] ✅(2026-06-24·ebba973) BUG-260624-01 残留:① MessageList:503 scrollToFirstPending return 过早修复(遍历全实例调,各内部 querySelector 无 pending 则 no-op,治 pending 落后续实例滚不到)+ ② ToolCardList listener 加 props.toolCalls.some(pending) 过滤短路(降大列表 N 实例×M emit 的 DOM 查询)。**scrollToFirstPending 时间窗去抖评估后不做**:同 id 连发被上游守卫拦截(AiDirAuthRequired/AiApprovalRequired useAiEvents:359/472 some 去重 + AiToolCallStarted:427 findToolCall 守卫),不同 id 连发是多 pending 合理场景每次滚到首个合理,vue-tsc EXIT 0。 +> 剩余低优 [ ](非阻断): > - [ ] BUG-260624-02 残留:审批超时倒计时 UI(5min 无倒计时,ToolCard pending 态显 mm:ss 到期前 60s 变红)+ 核 detached 窗口 emit 作用域(分离窗口是否独立 startListener,否则 ai-pending-arrived 收不到) -> - [x] ✅(2026-06-24·ebba973) MessageList:503 scrollToFirstPending return 过早已修(遍历全实例调,各内部 querySelector 无 pending 则 no-op,治 pending 落后续实例滚不到) > - [ ] F-09 根因:streaming/currentText 全局单例改 per-conv(根治多会话串扰,见 docs/待决策.md) -> - [x] ✅(2026-06-24 核验伪问题销账) CR-260622-01-P2-1 — **ChatMessage.timestamp 打戳"致时序错乱"核验为伪**。grep 全 src `.sort(` 零消息按 timestamp 排序:ai.ts:188 会话列表 updated_at / ConversationSidebar pinned / TopBar weight / MessageList:231 mention span start。**消息渲染顺序由 renderItems(store.state.messages 数组顺序)= DB list_by_conversation ORDER BY seq,不依赖 ChatMessage.timestamp**。timestamp 仅 formatRelative 展示(system 摘要显示插入时刻=压缩时刻,语义合理)。agent A 假设"前端按 timestamp 排序"不成立,非真实缺陷。 > - [ ] CR-260622-01-P2-2 [P2低优·评估降级] — **HTML/markdown/JSON 等非代码文件无 session 级缓存**。**2026-06-24 评估**:已有 `TOOL_RESULT_COMPRESS_ENABLED`(mod.rs:98/1029-1078 view-only 摘要,>2KB tool_result 压缩,LLM 视图摘要非全文回灌 prompt)部分缓解;read_symbol 治代码文件(主场景,降 24.4x)。非代码文件完整 session 缓存(path→hash+content+patch失效)设计复杂 + LLM patch 后重读确认行为不确定(缓存命中提示可能不够 LLM 仍重读),归 B 路线 prompt 策略(约束 patch 后不重读)更合适,工具侧完整缓存低优暂缓。 ---- - -### ✅ 2026-06-25 generating 状态机双轨收口 + 自动压缩路径残留修复 - -- [x] ✅(2026-06-25) **Task#1 自动压缩成功路径残留**(BUG-260624-05 衍生)— **AiCompressed 单事件被自动压缩路径误用 → 桌面端每次发送误弹 toast + 误刷整会话**。治法:`AiCompressed` 拆 `AiManualCompressed`(手动 IPC,3 处 emit 点,前端弹 toast)+ `AiAutoCompressed`(loop 自动,桌面静默仅复位 isCompressing 防按钮卡死,miniapp 仍插摘要气泡)。按语义拆事件而非按字段。— `src-tauri/src/commands/ai/mod.rs:207/212` -- [x] ✅(2026-06-25) **Task#2 双轨状态机收口** — `generating: bool` 字段(顶层单例 + PerConvState)与 `CONV_STATE_ENABLED` 灰度开关双轨全部删除,`ConvState` enum(`conv_state.rs`,5 态)成唯一真相源:写侧经 `transition_to` 守卫(非法转换拒绝),`GeneratingGuard` new/reset/drop 无条件迁移 + emit;读侧 `can_accept_request()` 接入 chat 域 4 入口(ai_chat_send/ai_regenerate/ai_chat_edit/ai_is_generating),比 `is_active()` 更严谨(覆盖 Stopping 态竞态)。开关常量已删,代码内 `CONV_STATE_ENABLED off 回退分支` 注释为说明性遗留(实际不可触发,待清理)。详案见 [generating状态机加固-2026-06-15.md §收口记录](./02-架构设计/专项设计/generating状态机加固-2026-06-15.md)。— `src-tauri/src/commands/ai/agentic/{conv_state.rs,guard.rs}` + `commands/ai/{mod.rs,commands/chat.rs}` - - --- ### 🔍 2026-06-26 灵感模块诊断待办(合并推进·单任务) > ✅ 全部修复完成(2026-06-27 核验) -- [x] **IDEA-FIX-01 [P0🔴]** — **ideas 表无软删除**。已对标 tasks/projects 的软删除全套模板实施。 -- [x] **IDEA-FIX-02 [P0🔴]** — **ideas 白名单含 id/created_at**。已对标 B-260616-16 移除。 -- [x] **IDEA-FIX-03 [P0🔴]** — **priority 无值域校验**。已加 `parse::() ∈ 0..=3` 校验。 -- [x] **IDEA-FIX-04 [P1🟡]** — **record_to_idea 有损转换**。已从 IdeaRecord 读真实 status/related_ids。 -- [x] **IDEA-FIX-05 [P1🟡]** — **评分关键词硬编码**。已拆到独立 `scoring_keywords.rs` const 文件。 -- [x] **IDEA-FIX-06 [P1🟡]** — **promote 补偿用 purge(不可恢复)**。已改为 soft_delete。 -- [x] **IDEA-FIX-07 [P1🟡]** — **关联单向**。已实现双向同步(关联时补入对方,解绑时同步移除)。 -- [x] **IDEA-FIX-08 [P1🟡]** — **创建表单无 tags 输入**。捕捉表单已加 tags 输入。 -- [x] **IDEA-FIX-09 [P2🟠]** — **创建表单缺 priority/source**。捕捉模态框已补 priority 下拉和 source 输入。 - [~] **IDEA-FIX-10 [P2🟠]** — **前端 filter + 后端分页漏数据**。团队决策:`hot`/`pending` 保留前端 filter(扩多值属性过度设计);keyword/order_by 已下沉后端。 -- [x] **IDEA-FIX-11 [P2🟠]** — **假雷达图**。i18n key 和 CSS class 已正名为 "score-bar"。 diff --git a/docs/待决策.md b/docs/待决策.md index 14f7cb8..1886724 100644 --- a/docs/待决策.md +++ b/docs/待决策.md @@ -25,71 +25,6 @@ ## 当前队列 -### ✅ 2026-06-26 任务图谱推进待决策(已按推荐方向全部决策) - -> 来源:用户 2026-06-26 多角度论证(基于代码实况) + 确认「按推荐方向决策」,7 DEC + 3 专题全部拍板。 -> **关键修正**:DEC-01 V28 已被灵感软删除(`ideas.deleted_at`,commit 005079f)占用 → 知识图谱用 **V29**;DEC-03 推荐由 a 修正为 **c**(启发式是过渡,接 LLM 评分后按需升 a)。 - -#### ✅ DEC-260626-01 父② 知识图谱 Phase 1 多工程合并 -- **决策**:**b** — 知识图谱独立 Phase 1(**V29**,V28 已被灵感软删除占用)。多工程代码零行(grep 全空),合并无理由,多工程后续 V30+。 -- **关联**:todo 父② -- **状态**:✅ 已决(2026-06-26) - -#### ✅ DEC-260626-02 ⑤.2 #7 promote 补偿删除方式 -- **决策**:**b** — 保留 purge。补偿是内部回滚建错的 project(非用户删除),purge 干净不污染回收站。 -- **关联**:todo ⑤.2 #7(标"保留 purge 不改") -- **状态**:✅ 已决(2026-06-26) - -#### ✅ DEC-260626-03 ⑤.2 #6 评分关键词配置形式 -- **决策**:**c** — 先拆 const 到独立文件(中/英两套,最小改动)。原推荐 a 修正:启发式是过渡(scoring.rs TODO 接 LLM 语义评分),接 LLM 后按需升 a(JSON 文件)。 -- **关联**:todo ⑤.2 #6 -- **状态**:✅ 已决(2026-06-26) - -#### ✅ DEC-260626-04 ⑤.2 #9/#10 表单补全交互 -- **决策**:**a** — tags 逗号分隔输入框(对标 Knowledge.vue 先例)+ priority 下拉 + source 输入。chip 组件留 UX 升级。 -- **关联**:todo ⑤.2 #9/#10 -- **状态**:✅ 已决(2026-06-26) - -#### ✅ DEC-260626-05 BUG-260620-05 层2 授权政策 -- **决策**:**a** — 维持方案①(白名单+弹窗三档)。层1(工程内免授权,F-260619-03 方案①)已解痛点,层2 黑名单制安全风险 + 分发冲突。 -- **关联**:todo BUG-260620-05 层2(标"维持①不做") -- **状态**:✅ 已决(2026-06-26) - -#### ✅ DEC-260626-06 父③ ToolCard 跨轮合并 + 审批状态机排期 -- **决策**:**b** — 等 G1 实测 + 父② 后再做。审批是高危路径,AiChat.vue 仍在频繁改动,择稳定窗口专项。 -- **关联**:todo 父③(排父②后) -- **状态**:✅ 已决(2026-06-26) - -#### ✅ DEC-260626-07 父④ F-09 per-conv 真多会话排期 -- **决策**:**a** — 父② Phase 1 后启动。F-09 是 Phase 4 前置,大改不与父② 并行(回归交叉)。 -- **关联**:todo 父④(排父②后) -- **状态**:✅ 已决(2026-06-26) - -#### ✅ 专题-1 workspace_root 分发适配 -- **决策**:**① 先行**(去默认白名单,dev 自用 projects.path 自动授权够),**分发前定②**(去相对锚)。 -- **状态**:✅ 已决(2026-06-26,① 立即/② 分发前) - -#### ✅ 专题-2 BUG-260623-03 审批 pending 超时兜底 -- **决策**:**c 无超时**(不做超时兜底)。用户:等待审批就是等待,一直等待,超时概念多余。审批是用户主动行为,不审批自然挂起等待,无需超时强制 reject/倒计时。 -- **状态**:✅ 已决(2026-06-26,不做超时,关闭 BUG-260623-03) - -#### ✅ 专题-3 F-09 streaming/currentText per-conv -- **决策**:**暂缓**(随 B 路线多会话并发同批做,即 DEC-07 父④)。 -- **状态**:✅ 已决(2026-06-26,归父④) - ---- - -#### T-260614-11 条件表达式引擎升级 -- **背景**:df-workflow conditions 仅支持 true/false 字面量,升级为真表达式求值。引擎设计好但无前端 UI 消费方(demoDag edges 无 condition 字段)。 -- **决策点**:求值器实现方式?(已暂缓:等工作流真实场景) -- **选项**: - - a: 手写求值器 - - b: 复活 set_skipped - - c: default false + warn(均认可) -- **推荐**:**⏸️ 暂缓**(无消费方,等 BuildNode 落地跑构建→测试→部署真实场景) -- **关联**:todo T-260614-11 -- **状态**:⏸️ 暂缓 - ### 🟡 待用户实测/澄清(需用户操作,主会话无法代办) #### S-260614-02 审批可见性实测重评 @@ -105,22 +40,19 @@ - **待用户操作**:部分场景运行时实测(A 路线场景 2/3)。 - **状态**:🟡 待用户实测 -#### S-260623-01 「自托管」含义澄清 -- **背景**:用户要求设置支持「自主执行(含高危)+ 自托管」。**自主执行含高危已实现** —— `autoExecuteMode` 三档 `low`/`medium`/`all`(AdvancedSection.vue F-#97, 2026-06-22),`all` = 完全 AI 接管含高危(delete/purge/run_command),选时二次确认。设置 → 高级 → AI 自动执行范围。但「自托管」grep 全项目无匹配,含义未明确。 -- **待用户澄清**:「自托管」指什么? -- **选项**: - - a: 自托管 AI 模型 endpoint(用户配自己的模型 API/本地模型,非默认 provider) - - b: 自管执行策略(= autoExecuteMode 已覆盖,无需另做) - - c: 自托管后端服务(用户自部署 df-relay/df-tunnel,非默认 `u-work.1216.top`) - - d: 其他(用户补充) -- **关联**:task105 / memory(设置自主执行已实现) -- **状态**:✅ **自用阶段定案(2026-06-23)** —— 用户决策:小程序当前自用(开发阶段),分发是产品终态但留后续(过渡期靠改 device_id 实现多机)。故「自托管」现阶段 = 已实现的自主执行(autoExecuteMode all 含高危),无另义。分发阶段再做配置层(MINIDEC-01)+ 配对(MINIDEC-02)。 - -#### ~~B-260617-11 tauri.conf.json 打包目标收窄确认~~ -- **状态**:✅ 已决(2026-06-18) — 已迁入归档。决策:A 临时本地构建,要求高速。提交前 revert 为 `"all"`。 - ### 🟡 待设计/架构演进(有决策点,需方案设计或长期规划) +#### T-260614-11 条件表达式引擎升级 +- **背景**:df-workflow conditions 仅支持 true/false 字面量,升级为真表达式求值。引擎设计好但无前端 UI 消费方(demoDag edges 无 condition 字段)。 +- **决策点**:求值器实现方式?(已暂缓:等工作流真实场景) +- **选项**: + - a: 手写求值器 + - b: 复活 set_skipped + - c: default false + warn(均认可) +- **推荐**:**⏸️ 暂缓**(无消费方,等 BuildNode 落地跑构建→测试→部署真实场景) +- **关联**:todo T-260614-11 +- **状态**:⏸️ 暂缓 + #### ARC-260615-07 架构重构批(排期/优先级决策) - **背景**:src-tauri IPC 编排层 5711 行成事实业务层,7 项架构债。df-core→df-types 已完成(CR-61),剩 6 项独立大改。 - **决策点**:6 项重构何时做/优先级/是否做(每项 ROI 与风险权衡,无法纯技术论证——做不做是资源/产品取舍) @@ -155,23 +87,6 @@ - **关联**:todo UX-260617-28 [INFO] - **状态**:⏸️ 长期(INFO,当前够用) -#### B-260618-03 路由解耦 cost_tier/intelligence(用户已全局决策去掉,工程大需专项) -- **背景**:用户 2026-06-18 决策「API 无判别依据的 cost_tier/intelligence 去掉,不写死不瞎填」。当前 router.rs 用 cost/intel 做硬过滤(步骤4 min_intelligence / 步骤5 max_cost)+ 排序(步骤7 Reverse(cost_tier)),但这两维度 100% 由 model_probe 瞎猜(预设表+启发式),零客观依据。GLM-5.2 被猜成 standard/medium(名不沾词素走兜底),title/compress 按 max_cost 过滤可能选不到合适模型。 -- **决策点**:用户已全局决策去掉。本项是落地(自主可推进)。记待决策因:工程大(~30 Edit)+ 主链路由行为变更需 cargo test router 全验证 + title/compress 选模型退化需在场核对。 -- **选项**: - - a: 自主推进(用户已决策,专项会话充分资源 + cargo test router 全验证 + 实测 title/compress 选模型) - - b: 等用户在场专项(核对路由行为退化 + 实测) -- **实施清单(勘察完成 2026-06-18)**: - - router.rs:TaskRequirements 删 min_intelligence(:29)/max_cost(:31)字段 + 删步骤4 filter(:62)/步骤5 filter(:63) + 步骤7 max_by_key 改纯 weight 删 Reverse(cost_tier)(:65) + 注释更新(:28/:31/:48-51/:55) - - 9 调用点删 min_intelligence/max_cost 传参:project.rs:535-536/631-632 · compress.rs:62-63 · knowledge_inject.rs:61-62/346-347 · agentic.rs:414-415 · title.rs:88-89 · ai_node.rs:203-204 · adversarial.rs:161-162 - - import 清理:调用点 CostTier/IntelligenceTier 不再用则删 import(title.rs/compress.rs 用 CostTier;多处用 IntelligenceTier) - - test:router.rs 多 test 改/删(max_cost_filters_expensive / max_cost_none_allows_any / same_weight_picks_cheaper_cost_tier / intelligence 相关 + TaskRequirements 构造删字段) - - ModelConfig.cost_tier 字段保留(B-04 model_probe 去瞎填处理数据源,不删字段) - - df-ai-core/model.rs 注释更新(:47/:60 提及 router max_cost/min_intelligence) -- **推荐**:**a 自主推进**(用户已全局决策,方向明确),建议专项会话充分资源;title/compress 失去 max_cost 约束后纯 weight 选模型,需核对 weight 配置合理(用户在 Settings 配) -- **关联**:todo B-260618-03/04/05 · UX-260618-04 前端 cost/intel 标签(依赖本项) -- **状态**:✅ **已实施**(2026-06-18 workflow wexu1isx1,主代核查 cargo check --workspace EXIT 0 + cargo test df-ai 109 passed + vue-tsc EXIT 0;SW-02 借用 E0502 主代修)。title/compress 纯 weight 选模型退化点:用户核对 Settings weight 配置(同 weight 并列 max_by_key 返回最后一个) - #### ARC-260618-01 God 文件拆分批(架构重投入·需设计) - **背景**:2026-06-18 架构坏味道扫描出 3 个 God 文件:`tool_registry.rs` 1091行单函数(SMELL-P0-2)/`AiChat.vue` 4026行单组件(SMELL-P0-3)/`crud.rs` 2212行(SMELL-P1-9)。均属"功能能跑但维护成本高/测试难"的技术债,非功能 bug。 - **决策点**:拆分何时做/优先级/拆分边界(每项拆分策略需专项设计,非小改;做不做是资源/可维护性取舍) @@ -194,41 +109,6 @@ - **关联**:todo SMELL-P1-6 - **状态**:🟡 待设计决策 -#### SW-260618-21 死代码预留功能清理批(清理 vs 保留) -- **背景**:2026-06-18 深层 sweep 核验出 4 类「0 外部消费者但设计预留」符号。清理减负 vs 保留未来功能取舍。注:`PendingApproval.diff` 经 IPC 活跃(useAiEvents:252 `event.diff→tc.diff` + ToolCard:44/754/772 渲染·UX-260618-06 审批 diff)**非死代码不删**;`transition_status` 已删(批次7·0 消费者+TODO 未实现)。 -- **核验(独立 grep 2026-06-18)**: - - `SessionState` enum + `session_state()`(mod.rs:172/393):0 外部调用·注释「读状态统一走 session_state() 收敛」设计预留(待重构 SW-02 类终态化复用) - - `AppState.releases`/`node_executions`(state.rs:206/210):`\.releases\b|\.node_executions\b` 0 字段访问·df-storage ReleaseRepo/NodeExecutionRepo 预留(未来 release mgmt/node exec log) - - ~~`PendingApproval.risk_level`(mod.rs:410)~~:**✅ 已删(2026-06-18·前端 types.ts 0 字段坐实真死·零波及·CR-22)** -- **决策点**:清理 0 消费者预留 vs 保留未来功能 -- **选项**: - - a: 全清理(删 SessionState/releases/node_executions/risk_level·连带 df-storage repo 定义·减 dead_code warning·但失去预留扩展点) - - b: 全保留+标 `#[allow(dead_code)]` 注释预留意图(消 warning·保留未来·零波及) - - c: 部分(SessionState/releases 近期无计划清·risk_level 核前端类型后定) -- **推荐**:**⏸️ b 保留+标 allow**(预留设计意图明确·清理失去未来扩展点 ROI 低;标 allow 消 warning 即可·零波及) -- **关联**:批次7 transition_status 已删 / CR-22 删 risk_level / CR-23 标 allow 5 处 -- **状态**:✅ 已实施 b(2026-06-18·主代自主决策·risk_level 删 + 预留/diff 标 allow·cargo 0 warning) - -#### F-260616-09-B 多会话并发架构 B 阶段实施决策(设计完成 2026-06-19·待拍板) -- **背景**:F-09 B 阶段设计文档完成 [F-09-多会话并发架构设计-2026-06-19.md](./02-架构设计/已编号方案/F-09-多会话并发架构设计-2026-06-19.md)。核验发现 **A 路线补漏已全部落地**(commands.rs:1404-1409 + useAiConversations.ts:80-83)→ **阶段1 跳过**。AiSession 12 字段。阶段2(B 主体,批1-8)待启动。 -- **决策点**: - - **⚠️ b-1(关键·须拍板)**:messages 是否 per-conv。原决策 b「messages 按 conv reload(不 per-conv)」与决策 e「切换不退出各自跑完」**矛盾**(单例 messages 下旧 loop push 污染新 conv,B-260615-11 退出校验必须保留→与 e 冲突)。设计推荐**修正 b 为 messages per-conv**(e 的技术必然必要条件,侵入面增量极小:ContextManager 挪 HashMap)。 - - **⚠️ c-1**:global permits=3 默认值 + Settings 加「并发会话数上限」配置项。 - - **e-1(主代已定✅)**:旧 loop save_conversation 保持原路径(save 接 conv_id 零改动)。低风险技术细节,主代裁决采纳。 -- **推荐**:**✅ b-1 采纳(messages per-conv,技术必然)+ c-1 保持 3 + Settings 配置 + e-1 原路径**。拍板后启动阶段2 批1(PerConvState 数据结构 + 访问器)。 -- **关联**:todo F-260616-09 / 设计文档 / memory aichat-arch-extensibility -- **状态**:✅ **主代自主裁决采纳(2026-06-19)** —— 用户授权「自主推进,能多角度确定的方案不等审批」。b-1 多角度论证充分(messages per-conv 是决策 e 技术必然必要条件,无替代);c-1 合理默认(global=3,Settings 配置后续);e-1 零改动技术细节。启动阶段2 批1。用户醒后可追认/推翻。 - -#### F-260619-05 任务可关联灵感(产品粒度/方向决策·todo 已登记) -- **背景**:tasks 表无 idea 关联字段;projects 已有 `idea_id REFERENCES ideas(id)` 模式。用户要任务关联灵感。todo.md 已登记(设计点+改动点+验收)。 -- **决策点**(产品取舍,需用户拍板): - - **粒度**:一对一(`source_idea_id`,任务来源单灵感)vs 一对多(`related_idea_ids` JSON,借鉴 releases.task_ids) - - **存储**:tasks 加 `idea_id` 列(迁移,复用 projects.idea_id 外键)vs 关联表 `task_idea_links`(多对多,灵活复杂) - - **方向**:单向(任务→灵感)vs 双向(灵感侧反向显示关联任务列表) -- **推荐**:**tasks 加 `idea_id REFERENCES ideas(id)`**(复用 projects 模式,1对1 起步,单向,后续按需扩展)。低侵入(单列迁移 + Repo 白名单 + 工具 idea_id 参数 + 前端展示)。 -- **关联**:todo.md F-260619-05(设计点详情) -- **状态**:✅ **已实施**(2026-06-20 调研确认)— tasks.idea_id 1对1 单向已落地:TaskRepo `idea_id` 字段(task_repo.rs:34 row.get / :52 INSERT / :63 UPDATE / :83 SELECT 全含)+ create_task 工具 idea_id 可选参数(tool_registry.rs:637「可选传 idea_id 关联灵感(1对1 单向)」)。复用 projects.idea_id 模式,粒度 1对1/单向/单列,符合推荐方向。前端任务卡片展示灵感来源(可选增强)待补。 - #### 消息级溯源 P2 切读策略(技术决策·大改需知情) - **背景**:消息级溯源 P0(地基 ai_messages 表 ✅ CR-10)+ P1(溯源字段 ✅ CR-12)完成。P2 切读是拆表实际启用(读写路径从 `ai_conversations.messages` JSON 切到 `ai_messages` 表)。 - **决策点**(技术策略,影响读写路径改造): @@ -251,7 +131,7 @@ ### df-miniapp 全功能审查(2026-06-23 workflow wll7qabgr + 主代补审) -> 6 区域并行审查(chat/会话页/useAiChat/连接层/组件/工程)。已**自主实施**:F1 看门狗 clearWatchdog 兜底 / F2 regenerate 守卫 / F3 flushCurrentText id 精确回填 / F9 stop 终态兜底 + 停止按钮(原无停止入口) / F21 连接状态中文文案 + 手动重连 / switch default 分支 / 二进制帧 warn / scheduleReconnect maxAttempts / 删 test 死页。以下为需决策项。 +> 6 区域并行审查(chat/会话页/useAiChat/连接层/组件/工程)。以下为仍待决策/暂缓项。 #### MINIDEC-260623-01 relay 配置层范围(F15·产品方向·分发卡点) - **背景**:config.ts relayHost/deviceId/token 全硬编码(setConfig 零调用方,storage 未接,无设置页)。当前填测试服 wss + 本机 device_id(联调期)。分发后用户无法改连自托管中继 → 连不上。 @@ -276,35 +156,6 @@ - **用户已定未来愿景(2026-06-23)**:分发阶段做完整配对授权,三步:① 设备端弹二维码 ② 小程序扫码识别并授权,双方来回点击确认 ③ 授权管理(设定授权的权限范围等)。本轮不做,留专项。 - **状态**:⏸️ **暂缓(2026-06-23)** —— 当前自用靠手抄 device_id;分发阶段按上述愿景做完整 QR 配对 + 权限范围管理(跨端 df-miniapp/df-relay/桌面端协同,专项立项)。 -#### MINIDEC-260623-03 WS 重连续流策略(F10·行为差异·需拍板) -- **背景**:watchdog 在 WS 重连后不重启。重连期间 device 续推 AiTextDelta 会丢(断连窗口)或半截文本错位。当前断连 onStatus 已 reset generating + clearWatchdog(连接断肯定停),但重连后续流未处理。 -- **决策点**:重连后续流策略? -- **选项**: - - a: onStatus 'connected' 主动发 load_messages 重新同步整个会话(最稳,但重置视图) - - b: 收到 AiTextDelta 时若 !generating 隐式恢复 + 补 assistant 占位(最平滑,有幽灵续流风险) - - c: 不处理(接受断连窗口该轮丢失) -- **推荐**:**c 现状**(MVP 断连窗口丢失可接受,a/b 复杂度高)。重度依赖移动端弱网再升级 a。 -- **状态**:✅ **已实施 a**(2026-06-23)—— 用户决策「断网不丢消息 + 从远端拉完整」。useAiChat syncOnConnect:ws 'connected' → 发 load_messages 拉完整历史(断网期间 missed 消息恢复 + 兼修冷启动空白 P1-C)。cargo check 0 + vue-tsc 0 + build DONE。 - -#### MINIDEC-260623-04 审批双源状态分裂统一(F4/F5/F6/F13·渲染源决策) -- **背景**:审批状态双写——pendingApprovals 数组 + messages[].toolCalls.status。AiApprovalRequired 双写为源头,AiError/switchConversation 只清 pendingApprovals 一侧(messages 内 tc 留陈旧 pending_approval)→ 可能渲染陈旧审批按钮,点 approve 命中已失效 tool_call。chat 页按 m.toolCalls 内联渲染审批按钮,pendingApprovals 仅顶部徽标计数。 -- **决策点**:统一单一渲染源? -- **选项**: - - a: 以 pendingApprovals 为准(messages tc 仅显示 status 不渲染按钮)—— 改 chat 页渲染逻辑(行为变更) - - b: switchConversation/AiError 清 pendingApprovals 时同步遍历 messages 把 pending_approval 标 rejected —— 改 useAiChat 状态清理(纯后端态,UI 不变) -- **推荐**:**b 同步清理**(最小行为变更,治陈旧按钮根因)。a 渲染源统一更彻底但 UI 改动大。 -- **关联**:BUG-260623-03(审批超时,后端侧) -- **状态**:✅ **已实施 a 单一渲染源**(2026-06-23)—— 用户决策「审批断网重连恢复卡片状态」。审批卡改从 pendingApprovals 独立面板渲染(与 messages 解耦,避重连 load_messages 替换 messages 的竞态);remote_bridge 加 sync_pending 路由(读 AiSession.pending_approvals 按 conv 过滤重发 AiApprovalRequired/AiDirAuthRequired);useAiChat onStatus 'connected' 清 pendingApprovals + 发 sync_pending 重建;handleEvent 加同 id 去重防竞态重复。内联工具卡保留状态徽标/参数/结果(按钮移面板)。cargo 0 + vue-tsc 0 + build DONE。 - -#### MINIDEC-260623-05 会话页管理缺口(删除/重命名·需后端命令) -- **背景**:conversations/index.vue 仅列表/切换/新建/下拉刷新。无删除/重命名(需后端 delete_conversation/rename_conversation 命令 + remote_bridge 路由,当前无)。 -- **决策点**:miniapp 是否需要会话管理? -- **选项**: - - a: 本轮加(后端命令 + bridge 路由 + 会话页长按菜单) - - b: 暂不做(用户回桌面端管理,MVP 仅查看/切换) -- **推荐**:**⏸️ b 暂不做**(miniapp 定位轻量操作终端,会话管理桌面端足够) -- **状态**:✅ **重命名已实施 / 删除暂缓**(2026-06-23)—— 用户决策「小程序对齐桌面端能改会话名」。remote_bridge 加 rename_conversation 路由(调 ai_conversation_rename + 推 AiConversationList 刷新);useAiChat renameConversation 方法(乐观本地更新);conversations/index.vue 长按会话 uni.showModal 编辑。删除会话暂不做(桌面端管理)。cargo 0 + vue-tsc 0 + build DONE。 - #### MINIDEC-260623-06 心跳/历史替换协议(F8/F20·跨模块·低优先) - **背景**:(F8)心跳活性检测依赖「任意入站消息」,低活跃场景每 ~60s 误判半开死连接触发无谓重连(relay 无 pong);(F20)AiMessageHistory 整体替换会丢弃正在进行的乐观气泡(device 主动推历史路径难区分 load 响应)。 - **决策点**:是否本轮改 relay 协议? @@ -315,15 +166,6 @@ - **推荐**:**⏸️ c 暂缓**(跨模块改动 ROI 低) - **状态**:⏸️ 暂缓 -#### MINIDEC-260623-07 regenerate 零调用方 + 备份组件漂移(P3 收尾) -- **背景**:(1) useAiChat.regenerate() 零调用方(全死,已加 generating 守卫防御);(2) MdView.vue/MentionInput.vue 因绕工具组件解析 bug 被 chat 页内联,源文件保留备用但已与内联实现漂移(MdView 无 mdCache、MentionInput 是 MVP 占位)——「拆回」时会引入旧实现。 -- **决策点**:regenerate 加 UI(重发按钮)or 删?备份组件同步/删/保留? -- **选项**: - - a: regenerate 加重发按钮 + 备份组件同步内联实现(或删) - - b: regenerate 删减负 + 备份组件保留标注释(预留工具 bug 修复后拆回) -- **推荐**:**b**(regenerate 删减负,备份保留预留意图)。或 a 视移动端重发需求。 -- **状态**:✅ **随 P1-F 选 a**(2026-06-23)—— 🟡 收尾批给 regenerate 加「重发」UI 入口(对齐桌面端),故保留函数不再删;备份组件(MdView/MentionInput)保留预留注释(绕工具解析 bug,未来拆回)。 - --- ## workspace_root 分发适配(编译期 CARGO_MANIFEST_DIR 写死,跨机器/跨平台失效) diff --git a/docs/待审查.md b/docs/待审查.md index 90c3028..da7f432 100644 --- a/docs/待审查.md +++ b/docs/待审查.md @@ -23,1758 +23,6 @@ ## 当前队列 -### CR-260624-01 AI 路径授权三档化(once/session/always) — ✅ 已审(PASS·单 agent + 6-agent 对抗深审 0 finding) - -- **范围**:AllowedDirs 加 `once` 层(state.rs:444)+ ai_authorize_dir decision 改 match 四分支 once/session/always(chat.rs:846)+ 执行后 clear_once(chat.rs:924)+ 前端 DirAuthDialog 加"当前会话"按钮 + useAiApproval.ts/api/ai.ts/api/types.ts decision 联合类型扩 'session' + i18n zh/en 加 dirAuthSession。 -- **维度**: - - (a) **once 清空时机**:clear_once 在 execute 后无条件清(state.rs:924 附近 chat.rs)。竞态:同 once 路径并发工具调用?现状工具串行(per_conv 单 generating),无并发,OK——但核验确认。 - - (b) **session vs once 语义边界**:session 切会话清(conversation.rs clear_session)/ once 执行后清 / reload_allowed_dirs 保留 both(state.rs:810 take once + session)。核 reload 是否漏保留 once。 - - (c) **is_authorized 读 once**:state.rs is_authorized 三层 `persistent || session || once` 任一命中。核 once 命中分支存在。 - - (d) **前端全链路**:DirAuthDialog handleAuthorize / useAiApproval approveToolCall / api.authorizeDir 三处 decision 联合类型均含 'session',types.ts decision: string 不受限(无类型保护,但 IPC 接受任意字符串,match _ 兜底)。 -- **核验方式**:grep `once|session` 贯穿 state.rs/chat.rs/前端 + 核 clear_once 调用点 + deny 分支(早 return,once 未写,不需清,核确认)。 -- **关注点**:批量 approve 仍用 once(单卡语义,不批量 always,useAiApproval:110);always 降级写 session(原降级 add_session,语义对齐)。 -- **审查结果**(2026-06-25):单 agent 审查(a2b2b35,6 维度全过)+ workflow 对抗深审(w9waeyg36,6 agent 并行 review + 每条 finding 独立 verify,**0 confirmed finding**)。once 清空时机(串行无并发误清)/ session-once 边界 / is_authorized 三层读 / deny 不触发 clear_once / 前端类型一致 全过。 - -### CR-260622-01 AI Chat 真实会话实测分析(84dc36c8·prompt 1.32M/completion 7.5K·3P0+3P1+2P2)— ✅ 已审(ISSUES·低优残留2项·P0清零) - -- **范围**:实测数据反推(**非静态代码审查**)。从生产 DB 会话 `84dc36c8`(D:\Downloads\kms,整理三知识库 HTML)全量 86 条消息反推上下文/工具/agent 行为缺陷。报告 `docs/05-代码审查/aichat-会话实测分析-2026-06-22.md`,分析脚本 `.temp/analyze_session.py`,DB 只读 `%APPDATA%/top.1216.devflow/devflow-dev.db`。 -- **维度**:(a) 独立核验 db 实测数据(token 比/重复 tool 结果/system 污染/时序);(b) 核验源码根因(报告 finding 均为"假设"待证)。 -- **核验方式**:只读 DB(`mode=ro`)+ grep/read 源码,**不信报告结论**(防上下文污染,[[code-review-anti-contamination]])。 -- **待审点(8)**: - - P0-1 压缩标记 `compressed` 未真降 prompt(1.32M)— 核 compress 模块是否替换为摘要还是仅打标记 - - P0-2 `patch_file`/`read_file` 部分路径裸吐文件全文(`raw 6204c`)非 JSON — 核 `tool_registry.rs` 返回序列化分支 - - P0-3 重复 tool 结果(seq 11-14 四份/69-71/83-85 三份同长度同秒)— 核 `ai_messages` 落库去重 / 流式 patch 合并 - - P1-1 过早宣称完成 3 次被推翻,无 checklist 自检 — agent 行为层 - - P1-2 末尾 #82→#85 tool 全返回后无 assistant 回复(中断)— 核 generating 复位/`try_continue` - - P1-3 三独立文件(mysql/redis/linux)整理本可并行却严格串行,无 fan-out 调度 — 印证 [[aichat-b-route-parallel-multiround]] - - P2-1 seq 0 system 消息内容污染 + 时序错乱(晚 seq 1 十小时)— 核 system 注入 + timestamp - - P2-2 同文件反复 read(mysql/linux 各 4×)无缓存 -- **最高杠杆**:P0-2(工具返回改 diff/changed 而非全文)→ prompt 降一个量级。 -- **关联**:[[aichat-techdebt-audit-2026-06-21]] [[devflow-generating-statemachine]] [[aichat-b-route-parallel-multiround]] [[aichat-decision-capability]] - ---- - -**复审结论(2026-06-24·3 agent 独立 grep/read 核验当前源码形态,防上下文污染)**: ✅ **ISSUES(低优残留,P0 清零可归档)** - -- **P0-1/P0-2/P0-3 已销账**(报告 06-23/06-24 修正段:压缩 is_active 白名单隐式修复 / truncate 破坏 JSON 修 / 重复 tool_result 实测 tool_call_id 全不同证伪) -- **P1-2 末尾中断 🟡缓解**:`guard.rs` GeneratingGuard RAII(正常 reset + Drop 兜底)+ ConvState 5态 enum 状态机已系统性覆盖"generating 散布复位 + 无 panic 兜底"原根因;报告 06-23 修正推翻原假设(84dc36c8 seq85 后疑 max_iterations 收敛/自然停,非 generating 卡死)。归 BUG-260623-03 审批 pending 无超时(待决策,推荐前端倒计时)。 -- **P2-1 system 时序 🟡部分缓解**:DB created_at 全消息统一 conv_created_at 不乱;但 `ChatMessage.timestamp`(provider.rs:44 system() 用插入时刻)system 摘要打戳不可靠 → 前端排序应弃 timestamp 改 seq/created_at。role 污染数据级偶发(压缩摘要 LLM 输出含 assistant 语义,非结构 bug)。 -- **P2-2 非代码文件缓存 🔴仍存**:read_symbol(7c2e3b2/7f6aa1e)治 .rs/.ts/.vue 代码文件(降 24.4x);HTML/markdown/JSON 等非代码文件无 session 级缓存,LLM 仍 read_file 全文回灌。 - -**待修项回流 todo**:🟡 2 项低优(CR-260622-01-P2-1 前端排序弃 timestamp / CR-260622-01-P2-2 非代码文件 session 缓存,归 B 路线 prompt 策略或工具侧缓存)。 - ---- - -### CR-260620-04 Agent 最大轮次支持「不限」(working tree 未提交:0=不限 约定·config.rs clamp 0-50 + mod.rs effective_max 0→usize::MAX + GeneralPanel checkbox + App.vue clamp + i18n labelUnlimited) — ✅ 已审(PASS·⚪2 WATCH·巡检 2026-06-20 独立 grep/read 核验控制流) - -- **范围**:新功能。约定 0=不限:后端 config.rs:69 clamp(1,50)→(0,50);mod.rs:542 loop 上界算 effective_max(0→usize::MAX,for 到不了上界,靠 stop_flag/收敛/审批退出,达上限暂停 AiMaxRoundsReached 不触发);前端 GeneralPanel 加「不限」checkbox(绑 agentMaxIterations===0)+ input :disabled + clamp 支持 0 + computed unlimited;App.vue:107 clamp 跟进(iter===0?0:...);i18n zh/en desc 加 0=不限 + labelUnlimited。 -- **维度**:正确性(effective_max 语义/达上限暂停分支 1172 在 0 模式是否漏退出)/ 边界(0 全链路 config→store→load→loop 透传)/ 回归(设 50 仍触发暂停?默认 10 不变)/ UI(checkbox 切换+持久化+disabled)/ 一致性(前端 0=不限 vs 后端 0→usize::MAX 对齐) -- **核验方式**:git diff working tree 5 文件逐行 + cargo check EXIT 0 + vue-tsc EXIT 0(主代已跑) -- **待审点**:effective_max 计算位置(542 for 前)/ usize::MAX 作 for 上界无溢出/意外 / unlimited computed set 与 syncAgentMaxIterations 协同 / App.vue 三元与 GeneralPanel clamp 语义一致 / 0 模式 1172 暂停分支确实不触发 / 不限模式无限轮风险仅靠 stop_flag/收敛/审批兜底(无软上限) - ---- - -**复审结论(2026-06-20·巡检独立 grep/read 核验控制流,不信"主代已跑"声明)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 WATCH 1 - -**① 核心控制流 0 模式暂停分支不误触发 PASS(深度核验,主会话待审点)**: -- `mod.rs:543 effective_max = if max_iterations==0 { usize::MAX } else { max_iterations }` ✅ -- `mod.rs:544 for iteration in start_iteration..effective_max` — 0 模式上界 MAX,for 仅能经 return/break 退出,不可能跑满上界 -- for 退出仅 3 路径:`stop_flag`→return(:559)/ 审批等待→return(:1162)/ **收敛→break**(`:1143 if !has_tool_calls { converged=true; break; }`) -- `mod.rs:1175 if !converged`(for 结束后)— 0 模式 for 仅经 converged break 到达此处(return 路径直接返回不经此),故 `!converged=false`,**跳过 AiMaxRoundsReached 暂停分支** ✅ -- 注释 `:1174` "max_iterations=0 时 effective_max=usize::MAX,for 不会正常结束至此,故不触发暂停" 准确 - -**② usize::MAX for 上界无溢出 PASS**: -- Rust Range `0..usize::MAX` iterator 到 MAX-1 后 next 返回 None(标准库保证不溢出) -- for 体内 `iteration` 仅作计数,无 `iteration+1` 致 MAX 溢出运算;实际 loop 靠 break/return 退出,不会真迭代至 MAX-1 - -**③ 0 全链路透传一致性 PASS**: -- 前端 GeneralPanel unlimited→`agentMaxIterations=0` → IPC → `config.rs:69 clamp(0,50)` store 0 → `chat.rs:167 load` → `run_agentic_loop max_iterations=0` → `effective_max=MAX` ✅ -- `App.vue:107 iter===0 ? 0 : Math.min(50,Math.max(1, iter??10))` 与 GeneralPanel `syncAgentMaxIterations`(`!==0` 透传)语义一致 ✅ -- GeneralPanel `unlimited` computed(get `===0` / set 0↔10)+ input `:disabled="unlimited"` + 持久化(appSettings SQLite,App.vue onMounted 读 0 透传后端) ✅ - -**④ 回归 PASS**: -- 默认 `DEFAULT_MAX_AGENT_ITERATIONS=10`(state.rs:556)不变,clamp 0-50 不影响默认 ✅ -- 设 50:effective_max=50,跑满未 break → `:1175 !converged` → 暂停触发(原行为保留)✅ -- 设 0:如 ① 不触发暂停 ✅ - -**⑤ i18n 中英对称 PASS**:en/zh `descAgentMaxIterations` 加 "0=不限" + `labelUnlimited` 新 key 中英对称 ✅ - ---- - -**⚪ LOW-1**:`config.rs:60-61` docstring stale — 仍写"范围双 clamp(command 端 1-50 + 前端 input min/max)",实际 `:67-69` 已改 clamp 0-50。文档与代码不符,建议同步。 - -**⚪ LOW-2**:`GeneralPanel` input `min="1" max="50"` 未改。unlimited 勾选时 `:disabled` 不冲突;用户不勾选直接手输 0 → v-model 允许 + sync `!==0` 透传(=不限语义自洽)。min=1 与"可手输 0"轻微矛盾,有 unlimited 复选框为主路径,影响低。 - -**WATCH-1**:0 模式(不限)**无软上限** — LLM 持续调工具不收敛时理论无限轮烧 token,仅靠 stop_flag/收敛/审批退出。用户主动选"不限"即接受此风险(设计取舍,非 bug)。建议 UI desc 补风险提示(当前 descAgentMaxIterations 仅"0=不限",未提烧 token 风险)。 - -- **待修项回流 todo**:**无** 🔴/🟡 项 - ---- - -### CR-260620-01 AI 授权目录改进批(working tree 未提交:strip_verbatim比对侧收口+黑名单统一+search_files symlink+bind reload+toast接线+单测·state.rs/tool_registry.rs/mod.rs/project.rs/chat.rs/AllowedDirsPanel.vue/Settings.vue/i18n×2) — ✅ 已审(ISSUES·🟡1⚪2) - -- **范围**:三方审查(安全/UX/跨端)交叉印证改进。误弹窗核心根因(strip_verbatim 仅写入侧,比对侧遗漏)收口到 `is_authorized` 单点(candidate strip)。黑名单两套不一致(validate_path contains vs is_in_system_blacklist 分段)统一单一来源 + 补 windows 根/programdata/.ssh。search_files symlink 逃逸(file_type 不跟随)。bind 路径 reload(project.rs create_with_binding/update_project/relocate 三处)。AllowedDirsPanel toast 接线 + pickDir 即加。P0 workspace_root 收紧已**回退**(编译期写死不适合分发,转待决策)。 -- **维度**:安全(授权绕过/黑名单/symlink 逃逸)+ 一致性(读写形态/黑名单统一)+ 回归(现有测试 + 新增单测) -- **核验方式**:`git diff` working tree 逐文件核验 + cargo test state:: 18 passed + vue-tsc EXIT 0 -- **待审点**:is_authorized strip 收口是否覆盖所有调用方(resolve :334 canon_resolved / check_path :448)/ validate_path 统一后 .ssh 等是否仍挡 / 黑名单 windows 段命中即拒是否误伤合法路径 / bind reload 是否遗漏 path 写入点 - ---- - -**复审结论(2026-06-20·主代独立 grep/read 核验源码形态 + cargo test state:: 18 passed)**: ✅ **ISSUES** — 🔴0 🟡1 ⚪2 - -**范围边界声明**:`git diff HEAD --stat` 显示本批实际改动 13 文件,其中 `AiChat.vue`(confirmNewConversation 去中断弹窗 + 删 isViewingGenerating dead code)+ `useAiConversations.ts`(newConversation 去 generatingConvs.clear)属 **F-09 决策e 真并发**改动,**非授权目录范围**,已由独立 **CR-260620-02** 登记(已审 PASS·⚪2),本审查不覆盖(避免审查污染/越界)。`Cargo.toml` 仅 LF/CRLF 行尾(warning 无实质改动)。本审查严格聚焦授权目录 8 文件。 - ---- - -**① is_authorized strip 收口覆盖性 PASS(核验 4 调用方)**: -- `state.rs:356-365 is_authorized` 比对侧 `strip_verbatim(candidate.to_path_buf())` 单点收口 ✅ -- `tool_registry.rs:326 is_authorized(&resolved)` — resolved 词法锚定(`PathBuf::from(path)` 或 `root.join(path)`),**无 verbatim 前缀**,strip 无害(原样返回),逻辑等价 ✅ -- `tool_registry.rs:334 is_authorized(&canon_resolved)` — canon_resolved 经 `std::fs::canonicalize` 返回带 `\\?\` 前缀,**strip 后命中** persistent(写入侧也 strip),形态一致 ✅(误弹窗核心根因修复点) -- `state.rs:460 check_path_authorization is_authorized(&resolved)` — resolved 词法层无前缀,strip 无害等价 ✅ -- `audit/mod.rs:187 check_path_authorization` 转调,同源 strip 生效 ✅ -- **覆盖完整**:4 个 is_authorized 调用点 + 1 个 check_path_authorization 全部经比对侧 strip,无遗漏调用方。 - -**② validate_path 凭据防护 PASS(统一后仍挡)**: -- `tool_registry.rs:136-138` `.ssh/.aws/.gnupg + Windows/ProgramFiles/ProgramData` 统一走 `crate::state::is_in_system_blacklist`(分段匹配,消除 contains 子串误伤如 `my\windows\backup`) ✅ -- `tool_registry.rs:140-142` AppData 保留 `lower.contains("\\appdata\\")` 单独挡(用户级数据,分段匹配会误伤 `D:\backup\appdata` 合法目录名 — 决策合理,已注释说明) ✅ -- `.ssh/.aws/.gnupg` 迁移后 `is_in_system_blacklist:422` 任意路径段命中即拒,validate_path 经 `PathBuf::from(&normalized)` 传入分段匹配,等价且更精准 ✅ -- **纵深防御完整**:validate_path(词法前置)+ is_authorized 内黑名单(运行时双保险)+ set_allowed_dirs:652 预校验(防持久化系统目录),三层防线齐全。 - -**③ 黑名单 windows 段命中即拒误伤面 PASS(分段匹配无子串误伤)**: -- `state.rs:408 if *seg == "windows" { return true }` — seg 是按 `/`/`\` 分隔的**完整路径段**,`E:\my-windows\backup` 分段为 ["e:","my-windows","backup"],`"my-windows" != "windows"` 不误伤 ✅ -- `test_blacklist_no_false_positive:892` 印证(`E:/my program files backup/x` 不拒)✅ -- `test_blacklist_windows_root_programdata_creds:983` 印证 win.ini/explorer.exe/ProgramData/.ssh/.aws/.gnupg 全拒 + devflow 自身 + `D:\backup\appdata` 不误伤 ✅ -- Windows 根段命中即拒(含根本身,不再限定 system32 子目录):防 win.ini/hosts 等敏感文件,合理增强 ✅ -- **`.ssh/.aws/.gnupg` 段在 `cfg!(windows)` 块外**(跨平台):设计正确,Unix 凭据目录同样需挡 ✅ - -**④ search_files_recursive symlink 逃逸 PASS**: -- `tool_registry.rs:1797-1804` 改 `entry.file_type().await?`(不跟随 symlink)+ symlink 一律 `continue`(不匹配/不递归),消除 workspace 内 symlink → 外部目录的递归逃逸 ✅ -- metadata 延迟到命中后才 `entry.metadata().await?`(避免对每个条目都跟随 symlink 读元数据,性能 + 安全双收益) ✅ -- 与 `list_dir_recursive` 行为对齐(注释 :1798 标注)✅ - -**⑤ P0 回退完整性 PASS(workspace_root 三处同源)**: -- `state.rs:490` / `tool_registry.rs:275` / `mod.rs:282` 三处均用 `env!("CARGO_MANIFEST_DIR").parent().parent()`,**无残留 project_root_path** ✅ -- `state.rs:485-489` 加分发适配警告注释(编译期写死不适合分发,转待决策),诚实标注已知限制 ✅ -- `mod.rs:281 workspace_root_str` 额外 canonicalize(trust key 归一化用,与 run_command 默认 working_dir 对齐),用途独立于 is_authorized 比对,无需 strip ✅ -- **default_with_root:340/reload:619 空白名单时保留 workspace_root** 兜底,向后兼容(开发机自用有效)✅ - -**⑥ set_allowed_dirs/get_allowed_dirs 一致性 PASS**: -- `state.rs:664-665 set_allowed_dirs` 返回时过滤 workspace_root(与 `get_allowed_dirs:680` 一致),内部根不暴露前端 ✅ -- AllowedDirsPanel.vue:74 删 `isWorkspaceRoot` 前端宽松匹配(原 `endsWith('/src-tauri')` 在分发场景失效),改由后端统一过滤,前后端读写形态一致 ✅ - -**⑦ AllowedDirsPanel 即时持久化 + toast PASS**: -- emit toast 通道(Settings.vue `@toast="showToast"`),替代 console.error 用户零反馈 ✅ -- pickDir 选完即加(消除二次点"添加"摩擦)+ 单按钮双态(newDir 空→选目录/有值→添加),UX 改进 ✅ -- dirs 由 `setAllowedDirs` 返回值回填(canonicalize + strip 后真实路径),读写不漂移 ✅ -- i18n zh-CN/en settings.ts 4 toast key + allowedDirsSelectDir + desc 对齐(workspace_root→devflow 自身)中英对称 ✅ - -**⑧ chat.rs:552 docstring 修正 PASS**:PerConvState 已删,改 AppState.session 进程级会话临时授权语义,文档与代码一致 ✅ - -**⑨ 回归 PASS**:cargo test state:: **18 passed 0 failed**(含 3 新增 + 15 原有)。test_blacklist_windows_system32:878 在新"windows 段即拒"逻辑下仍 pass(更早 return true)。test_blacklist_no_false_positive 在 `.ssh/.aws/.gnupg` 增强后仍 pass(测试用例合法路径不含这些段)✅ - ---- - -**🟡 MED-1:bind_directory / create_project(path) AI 工具路径 reload 残留(注释承诺未兑现)** - -| file:line | 问题 | 建议 | -|-----------|------|------| -| `tool_registry.rs:545-549`(bind_directory handler 注释)+ `:523-525`(create_project 带 path 路径) | **注释承诺"通过 app_handle emit 事件让 state reload,或下次工具调用前 reload 兜底",但代码未实际触发 reload**。bind_dir_to_project:375 经 `repo.update_field(id, "path", path)` 直接写 DB,**绕过 project.rs IPC**(create_with_binding/update_project/relocate 三处 reload 覆盖不到)。register_project_tools 闭包无 AppState 参数,确实无法直接 reload。reload_allowed_dirs 仅在启动(state.rs:553 init)+ project.rs 三 IPC + set_allowed_dirs(:661)触发,**"下次工具调用前 reload 兜底"是空头承诺** — 无该机制(主代 grep reload_allowed_dirs 全 src 印证,无工具调用前置 reload 路径)。后果:AI 调 bind_directory 绑定新目录后,该目录未进内存白名单,后续文件工具仍误弹窗,直到用户手动点 Reload 或重启。 | 二选一:(a) register_project_tools 闭包捕获 AppState 句柄(若可获取),bind_dir_to_project 后调 `state.reload_allowed_dirs().await`;(b) process_tool_calls 前置轻量校验:每次工具调用前比对 projects.bind_directory 与内存白名单差集,差集非空触发 reload(兑现"下次工具调用前 reload 兜底"注释)。当前不影响安全(只是 UX 误弹窗,用户可手动 Reload),降 MED。 | - -**⚪ LOW-1:strip_verbatim 单前缀 strip + 大小写敏感(理论边界,实际安全)** - -`state.rs:471-478 strip_verbatim` 用 `s.strip_prefix(prefix)`(prefix 为 `\\?\`/`\\.\`/`\??\` ASCII 小写),Windows OS 生成的 verbatim 前缀**始终固定小写**(Rust std canonicalize 保证),大小写敏感 strip 不会漏。仅 strip 一个前缀(无嵌套场景)。理论边界:若 LLM 传入大写 `\\?\`(非 canonicalize 来源)则 strip 失败 — 但 LLM 传入路径走 validate_path + check_path_authorization 词法层,不经 canonicalize,不会带 verbatim 前缀。实际安全,降 LOW 标注。 - -**⚪ LOW-2:reload_allowed_dirs 项目绑定目录读 list_all(无 active 过滤)** - -`state.rs:601-606 reload_allowed_dirs` 读 `repo.list_all()` 全量项目(含回收站软删项目?)的 bind_directory 合并入 persistent。若软删项目 path 仍被读入,会残留无效授权目录(指向已"删除"项目路径)。建议核验 ProjectRepo::list_all 是否过滤 `status != 'deleted'`,或 reload 时 filter active 项目。当前影响:无害(目录仍存在,授权不影响安全,仅白名单略冗余),降 LOW。 - ---- - -- **🟡 MED-1**: bind_directory / create_project(path) AI 工具路径 reload 残留(注释承诺未兑现,UX 误弹窗,非安全) -- **⚪ LOW-1**: strip_verbatim 大小写敏感 strip 理论边界(实际 OS 固定小写,安全) -- **⚪ LOW-2**: reload_allowed_dirs 读 list_all 可能含软删项目 path(白名单略冗余,无害) -- **待修项回流 todo**: 🟡 **1 项**(tool_registry.rs:545-549 bind_directory/create_project AI 工具路径 reload 兑现 — 二选一:闭包捕获 AppState / process_tool_calls 前置差集 reload) - -### CR-260620-02 F-09 决策e newConversation 跟进(working tree 未提交:AiChat.vue confirmNewConversation 去中断弹窗+删 isViewingGenerating dead code / useAiConversations.ts newConversation 去 generatingConvs.clear()) — ✅ 已审(PASS·⚪2) - -- **复审结论(2026-06-20·审查 agent 独立 git diff + grep/read 核验源码形态)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 -- **验证**: `git diff` AiChat.vue/useAiConversations.ts 逐行核验 + vue-tsc --noEmit EXIT 0 + 独立 grep 全 src/ 无残留。 - -**① confirmNewConversation 调用路径 PASS**: 唯二调用方均正确直达 `store.newConversation()`: -- AiChat.vue:7 `@confirm-new-conversation="confirmNewConversation"`(ConversationSidebar emit) -- AiChat.vue:18 `@new-conversation="confirmNewConversation"`(TopBar emit) -- 去弹窗后 handler 体仅剩 `await store.newConversation()`(:248-250),无残留 isViewingGenerating/confirmDialog 中断逻辑 ✅ - -**② isViewingGenerating 删除完整性 PASS**: grep 全 src/ 命中 AiChat.vue 已无 isViewingGenerating。其余 3 处命中(MessageList.vue:196 / MaxRoundsCard.vue:42 / DirAuthDialog.vue:50)均为各组件**本地独立 computed**(`store.state.streaming && store.isGenerating(state.activeConversationId)`),非 import AiChat 的——AiChat 删除不影响它们。vue-i18n/tsconfig noUnusedLocals 编译通过(vue-tsc EXIT 0)✅ - -**③ newConversation 去 clear 后旧 conv 收尾 PASS**: 事件路由按 conversation_id 正确分发,旧 conv 后台 loop 完成时收尾无依赖 generatingConvs 全清: -- useAiEvents.ts:461-469 `handleEvent` 外围 — 非当前会话事件(`!isCurrent`)仅 AiCompleted/AiError 执行 `state.generatingConvs.delete(convId)` + `loadConversations()`,不污染当前视图 ✅ -- useAiEvents.ts:362(AiCompleted)/ :415(AiError)— `state.generatingConvs.delete(event.conversation_id || '')` 按 conv_id 删除(per-conv),非全清 ✅ -- 去 clear 后旧 conv 后台完成时按 conv_id 正确从 Set 移除,侧栏态正确刷新(侧栏 `isGenerating(id)` 读 Set,双会话生成可见)✅ - -**④ switchConversation 对齐 PASS**: newConversation 真对齐 switchConversation 并行语义。两者均:不中断后台 loop / 不清 generatingConvs(per-conv 跟踪保留) / 事件按 conv_id 路由。switchConversation:91-205 grep 核验无 generatingConvs.clear() / 无 loop 杀逻辑 ✅ - -**⑤ queue 清空边界 PASS(含 WATCH)**: `state.queue`(ai.ts:90)是**单一全局数组**非 per-conv。drainQueue(useAiSend.ts:246-267)调 sendMessage 续发,路由到 `state.activeConversationId`(非队列起源 conv)。故 queue 语义是"active-conv 视图的待发意图"非跨 conv 跟踪。newConversation 清 `queue = []`(:80)实际**正确且更安全**:防 conv A 排队消息在 conv A 的 AiCompleted 触发 ai-drain-queue 后误发到新 conv B(注释"防旧会话排队消息带进新会话 drain"准确)。**🟡→⚪ WATCH-1**:switchConversation(:91-205)**不清 queue**,真并发下若 conv A 排队消息 + 切到 conv B,A 的 AiCompleted 会把队列项发到 B(误路由,既有逻辑非本 CR 引入,登记待评估)。 - -**⑥ 后端依赖 PASS**: 独立 Read conversation.rs:154-201 `ai_conversation_create` 确认决策e 后端已落地: -- :163-166 注释明确「新建会话不杀旧 loop」(B-260615-10 单 loop 软复位废弃) -- :188 仅切 `active_conversation_id = Some(id.clone())` -- :196-198 注释「旧 conv 的 per_conv 不清除(决策e:后台 loop 继续跑)」+ :198 `session.conv(&id)` 为新会话建独立 per_conv -- 前端去弹窗/去 clear 跟进无后端依赖,后端先行已就绪 ✅ - -**⑦ confirmDialog 基础设施不受影响 PASS**: grep AiChat.vue confirmDialog 仍有 5 活跃消费方(:230 confirmDeleteConversation / :236 confirmClearChat / :376 tryForceSend / :545 handleClearContext 等),useConfirm 模块仍 import(:136)。仅 confirmNewConversation 退出消费,无回归 ✅ - -- **⚪ WATCH-1**: queue 全局数组非 per-conv。switchConversation 不清 queue,真并发下「conv A 排队 + 切 conv B」时 A 的 AiCompleted→ai-drain-queue 会把队列项误发到 B。newConversation 清 queue 是正确收口但两路径行为不一致。建议统一(switchConversation 也清 queue,或 queue 改 per-conv)。既有逻辑非本 CR 引入。 -- **⚪ LOW-1**: stale doc 注释。去弹窗后三处子组件注释仍描述旧行为(「带二次确认」/「生成中走确认弹层」/「生成中需二次确认」):TopBar.vue:163 / ConversationSidebar.vue:226 / ConversationSidebar.vue:242。纯文档不影响行为,建议顺手同步。另 i18n key `confirmNewConvWhileGenerating`(zh-CN:120 / en:119)已成死 key(0 消费方,仅 2 文件定义残留),可选清理。 -- **待修项回流 todo**: **无** 🔴/🟡 项 - -### CR-260618-01 batch68 未提交改动批量(41文件+754/-2434:虚拟滚动删除+timestamp打戳+audit吞错修复+fetch_models合并+分组折叠+todo精简) — ✅ 已审(PASS·🟡1 WATCH) - -- **复审结论(2026-06-18·主代独立 grep/read 核验 diff)**: ✅ **PASS** — 🔴0 🟡1 ⚪2 -- **验证**: git diff HEAD 逐文件核验(41 文件)。 - -**① provider.rs timestamp 打戳 PASS**: 非 reasoning_content(已在 74003c 提交)。本批新增 `timestamp: Option` 字段 + `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;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 + 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>> · `: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>>> · `:125` per_conv_permits:Arc(热改) · `:150-158` acquire_per_conv lock HashMap → or_insert_with(permits 当前值)→ clone Arc → 释放 lock → acquire_owned · `:165-167` release_conv lock+remove · `:178-182` set_per_conv store AtomicUsize + clear HashMap(软收敛) | ✅ | -| C-release_conv时机 | 仅 ai_conversation_delete 调用(conv 删即清) | `commands.rs:1737` `state.llm_concurrency.release_conv(&conversation_id).await` 在 ai_conversation_delete 内 · `:1726` per_conv.remove 同步 · `:1736` drop(session) 再取 LlmConcurrency 锁(避免锁序) · 注释 :1731-1735 说明 loop 正常收敛/达 MAX/stop 但 conv 未删不清理(复用限流计数) | ✅ | -| D-F-260616-12 retry | retry 同 loop 内持 per_conv 合理 | `agentic.rs:521` acquire_per_conv(&conv_id) loop 入口 · `:512-519` 注释明确「retry 同 loop 内,持 per_conv 合理;global 是会话级,retry 不再阻塞他对话(原每轮 acquire/drop 语义下 global 短暂释放,现整 loop 持有更贴合会话级并发上限语义)」 · retry 在 stream_one_provider 内(:236 重试),同 loop 同 conv,permit 不释放正确 | ✅ | -| E-两HashMap独立 | AiSession.per_conv(状态) vs LlmConcurrency.per_conv(限流) | `mod.rs:357` AiSession.per_conv:HashMap(会话状态真相源) · `state.rs:120` LlmConcurrency.per_conv:Arc>>>(限流槽) · 两者独立 HashMap 无耦合 · 批2 双写 AiSession(per_conv 状态+顶层桥接)与 batch5 限流无冲突 | ✅ | -| F-set_per_conv软收敛 | 热改 permits + 清 HashMap | `state.rs:174-182` set_per_conv store AtomicUsize + clear HashMap · 注释 :175-177「旧 permit 随 Drop 释放,新对话 acquire 用新 permits 重建 Semaphore;已建对话若仍在跑,旧 Semaphore 不变,下次该 conv 新 acquire 时因 HashMap 已清空会重建为新 permits」 · 软收敛语义正确(旧 permit 不抢占) · `commands.rs:1886` ai_set_concurrency_config 调 set_per_conv | ✅ | -| G-compress/title/knowledge接入 | 签名 conv_id + acquire_per_conv | `compress.rs:85` acquire_per_conv(conv_id) · `title.rs:190` acquire_per_conv(conv_id)(注释 :188 per_conv 改 HashMap,标题针对本对话) · `knowledge_inject.rs:393` acquire_per_conv(conv_id)(注释 :391 知识提炼针对本对话) · 三处共享同 conv 限流槽(permits=2 容纳主 loop + 1 后台) | ✅ | -| H-project合成key | 扫描用 "__project_scan__" 合成 key | `project.rs:553/649` acquire_per_conv("__project_scan__") 合成 key · 非真实 conv_id 但统一限流(扫描任务共享一槽,防并发扫描失控) | ✅ | -| I-编译/测试 | cargo check 0 warning | `cargo check --workspace` EXIT 0 **0 warning** | ✅ | - -**对抗核验印证**: -- **global 整 loop 持有(含审批等待)?** ✅ acquire_global 在 for iteration 外(:520 loop 入口),审批等待 return(:991)/收敛/达 MAX/stop 各 return 点函数返回,_conv_global_permit Drop 释放槽位。 -- **审批等待占 global 槽?** ⚠️ **修正 prompt WATCH⑥**:审批等待路径(:988-991)disarm guard + `return`,函数返回释放 _conv_global_permit/_conv_per_conv_permit。**审批等待期间不占 global 槽**,续跑 try_continue → run_agentic_loop(:1216)重新 acquire。prompt WATCH⑥「审批等待占 global 槽阻塞他对话」**描述不准确**,实际审批等待释放槽位不阻塞他对话——这是**合理设计**(审批等待是用户交互态,不应占并发槽)。详见 WATCH-1。 -- **per_conv permits=2 容量够?** ✅ 主 loop stream_llm(1)+ 后台标题/压缩/提炼(各 1)同 conv 共享 permits=2。常态主 loop 持 1 + 至多 1 后台任务(标题或压缩或提炼之一)持 1 = 2 满载,第 3 个后台任务 await 排队。单对话内并发上限 2 合理(防失控)。 -- **release_conv 仅 delete 无残留?** ✅ ai_conversation_delete(:1737)conv 删即清 LlmConcurrency.per_conv 条目。已持 permit 不受影响(permit 绑旧 Arc 随 Drop 释放),仅阻止新条目累积防 HashMap 无限增长。loop 正常收敛但 conv 未删时不清理(下次发消息复用,限流计数连续)。 -- **set_per_conv 清 HashMap 是否丢正在跑对话的限流?** ✅ 软收敛:已建对话旧 Semaphore 不变(permit 绑旧 Arc),HashMap 清空仅影响新 acquire(重建为新 permits)。旧 Semaphore 随最后 permit 释放而 drop。无丢限流风险。 -- **与批2 双写兼容?** ✅ batch5 仅改 LlmConcurrency(state.rs)+ 接入点 acquire/release,不动 AiSession.per_conv 状态结构。两 HashMap 独立,批2 双写 AiSession(per_conv 状态+顶层)与 batch5 限流无耦合。cargo check 0 warning 印证兼容。 - -**⚪ WATCH**: -1. **审批等待占 global 槽(修正)**:prompt WATCH⑥ 声称「审批等待占 global 槽阻塞他对话(决策c有意)」**描述不准确**。实际审批等待路径(agentic.rs:988-991)disarm guard + return,函数栈帧退出释放 _conv_global_permit,**审批等待不占 global 槽**。续跑 try_continue→run_agentic_loop 重新 acquire。这是合理设计(审批是用户交互态不占并发槽)。**无问题**,仅修正 prompt 描述。另:达 MAX 未收敛转暂停态(:1003- 路径)同样 return 释放槽位,续跑 ai_continue_loop→try_continue→run_agentic_loop 重新 acquire,一致。 - -- **关联**:F-09 阶段2 batch5(决策c-1 落地)。 -- **待修项回流 todo**: **无** 🔴/🟡 项(WATCH-1 为 prompt 描述修正,非代码问题) - -### CR-260619-06 F-09 batch8 启动恢复多conv restore+L0清残留(纯后端)(agent f09-batch8·主代兜底·commit 6ad4ec2) — ✅ 已审(PASS·⚪1 WATCH + 🟡1 跨批遗留·独立 grep/read + git diff 时序对抗核验·不跑 cargo 避 batch5 中间态) - -- **范围**(2 文件):audit.rs restore_pending_approvals 多 conv 分配(按 conversation_id 分组惰性建/复用 PerConvState,无主 None 不建 R-9)+ 2 单测(分配不变量/全 None 无 per_conv);lib.rs L0 握手遍历 per_conv 清多 conv 残留 generating(dirty_convs + 逐个复位 + 各发 AiCompleted 补偿 + active 兜底)。 -- **审查要点**:① restore 多 conv 分配正确性(convs_restored 去重 + conv() 幂等惰性建);② 无主 None 不建 per_conv(R-9);③ L0 遍历全 per_conv(非单 active)清残留;④ 各 dirty conv AiCompleted 补偿(按 conversation_id 路由);⑤ active 兜底(双写期边界);⑥ 与双写兼容(顶层双写复位);⑦ 不改 IPC 签名/前端/顶层字段。 -- **验证(主代兜底)**:cargo check --workspace 0 + test 98(96+2 新)+ grep restore 多 conv/L0 遍历印证。 -- **关联**:F-09 阶段2 batch8(**F-09 后端完整**;batch4 启用·前后端待用户)。 - -**复审结论(2026-06-19·独立 grep/read 核验源码当前形态 + git diff 跨版本时序对抗·commit 6ad4ec2·**不跑 cargo** 避 batch5 llm_concurrency 中间态误报·memory [[review-batching-worktree-transient]])**: ✅ **PASS** — 🔴0 🟡1(跨批遗留·非 batch8 引入)⚪1 - -**逐项核验表(file:line 佐证 + 判定)**: - -| 项 | 核验点 | 佐证 | 判定 | -|---|---|---|---| -| A-restore 多 conv 分配 | convs_restored HashSet 去重 + conv() 幂等惰性建 | `audit.rs:306` `let mut convs_restored: HashSet` · `: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` · `conversation_repo.rs:242 list_pending()` 返 `Vec` · `:95 from_row` `conversation_id: row.get("conversation_id")?` 真实读 DB 列 | ✅ | -| E-L0 遍历全 per_conv | 非 active 单值,遍历 HashMap 全 dirty conv | `lib.rs:47-52` `let mut dirty_convs: Vec = session.per_conv.iter().filter(|(_,c)| 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 真实存在);`:67 !dirty_convs.contains(&active)` 守卫防重复 emit | ✅ | -| H-AiCompleted 补偿路由 | 各 dirty conv 各发一个,按 conversation_id 路由 | `lib.rs:72-87` `if was_generating { for cid in &dirty_convs { let _ = app_h.emit("ai-chat-event", AiChatEvent::AiCompleted{..conversation_id:Some(cid.clone())}); } }` — AiCompleted 变体含 conversation_id 字段(mod.rs:118-125),各 cid 各 emit;`:53 was_generating = !dirty_convs.is_empty() \|\| session.generating` 守卫无残留不空发 | ✅ | -| I-2 单测有效性 | 分配不变量 + 全 None 边界 | `audit.rs:864-940 test_restore_multi_conv_distribution_invariant`:4 pending 行(2 conv-a + 1 conv-b + 1 None)复现分配逻辑 → 断言 per_conv.len()=2 / conv_read 可达 / 同 conv 复用同指针(幂等 `as *const _` 相等) / pending_approvals.len()=4(含无主) · `audit.rs:943-958 test_restore_only_ownerless_no_per_conv`:全 None → 断言 per_conv.is_empty() + convs_restored.is_empty()。两测逐字复现实现分配逻辑,契约对齐 conv()/conv_read() | ✅ | -| J-不改 IPC/前端/顶层字段 | 纯后端,无 IPC 签名变更 | `git show 6ad4ec2 --stat` 仅 audit.rs(+131)/lib.rs(+75/-29 改 setup listener);无 commands.rs IPC handler 变更 / 无前端 ts 改动 / 无 mod.rs PerConvState 字段增删(复用批1 struct) | ✅ | - -**对抗核验印证**: -- **convs_restored 去重 vs conv() 幂等双重保险?** ✅ 双重保险但非冗余:convs_restored.insert 首次返 true 触发 conv() 一次;conv() 内 entry().or_insert_with 本身幂等(已存在不重建)。即便去重失效(理论),conv() 仍幂等。双重防御,正确性无依赖单一机制。 -- **L0 遍历是否真覆盖多 conv?** ✅ `session.per_conv.iter()` 遍历 HashMap 全部条目,filter generating 收集所有 dirty conv,非单 active 读。注释 :43-46 显式声明「批2 前仅读 active per_conv.generating 单值;批8 改遍历全部 per_conv 各归各复位」。 -- **批5 llm_concurrency 中间态冲突?** ✅ batch5 改 LlmConcurrency(state.rs per_conv Semaphore HashMap),与 batch8 改 AiSession.per_conv(状态) + lib.rs L0 listener 完全独立。两 HashMap 独立无耦合(CR-260619-05 E 项已印证)。本次不跑 cargo 正是避 batch5 调整中间态致误报。 -- **restore 在 session.lock 内调 conv() 借用安全?** ✅ `audit.rs:302 let mut session = state.ai_session.lock().await` 取独占锁,`:321 session.conv(cid)` 取 &mut PerConvState 在锁内单次借用,`:324 session.pending_approvals.insert` 另一字段借用——两次顺序借用(非同时),Rust 借用检查通过(主代 cargo check --workspace EXIT 0 印证,非本审查跑)。 -- **filter `!cid.is_empty()` 必要性?** ✅ 防空串 conv_id 惰性建无意义 per_conv(空串非合法 conv_id);conv() 本身不校验空串,restore 侧显式守卫合理防御。 - -**🔴→🟡 时序对抗降级(L0 clear 致冷启动审批丢失)— 非批8 引入,跨批遗留**: - -冷启动时序核验确认「L0 clear 抹掉 restore 重建的 pending」**潜在路径存在**,但**归属判定修正**: - -| 环节 | 核验 | 佐证 | -|---|---|---| -| 1. restore 填充 | ✅ 真实 | `state.rs:340 restore_pending_approvals(&state).await` 在 init 内 · `lib.rs:25 block_on(AppState::init)` 同步执行 · `audit.rs:291` 从 DB list_pending 重建到 `session.pending_approvals` 内存 | -| 2. L0 clear 触发 | ✅ 真实 | `useAiEvents.ts:386 emit('ai-client-ready')` 在 startListener 内(AiChat onMounted 必挂载触发)→ `lib.rs:38 listener` → `lib.rs:62 session.pending_approvals.clear()` 无条件清空 | -| 3. 前端不显示 | ✅ 真实 | `ai_pending_tool_calls`(commands.rs:507-517)数据源 = `session.pending_approvals.values()` **内存**(非 DB),clear 后返空 | -| 4. 审批报错 | ⚠️ **部分不成立** | `ai_approve`(commands.rs:328 内存 remove None)→ `:334 find_by_tool_call_id` 查 DB status=pending → `:336` 白名单「executed/rejected/failed」不含 pending → `:346 return Err「未找到挂起的审批」`。**但**前端步骤 3 已拉不到 pending,用户**根本看不到审批卡**不会触发 ai_approve,步骤 4 是理论路径非实际触发 | - -**🔴→🟡 归属修正(推翻「batch8 引入回归」判定)**: - -`git show 5c15b72:src-tauri/src/lib.rs`(批2,即 batch8 前一版)L0 listener **已有无条件 clear pending**: -- `if was_generating { ...; session.pending_approvals.clear(); ... }`(was_generating=true 分支) -- `else { session.pending_approvals.clear(); }`(was_generating=false 分支) -- **两分支都 clear = 无条件 clear**(行为等价 batch8 `:62` 外层单处 clear) - -`git show 6ad4ec2 -- src-tauri/src/lib.rs` diff 印证:batch8 仅**重构 clear 位置**(批2 两分支各一处 → batch8 外层一处合并),`session.pending_approvals.clear()` 出现次数 2→1(**净减**,非新增)。**batch8 未引入 clear,更未引入回归**——潜在冷启动审批丢失问题**批2 起就存在**,属跨批遗留。 - -`git log -S "session.pending_approvals.clear()" -- src-tauri/src/lib.rs` 印证:该字符串首次引入在 `d2cb38c`(任务推进链,B-260616-01 L0 握手首引),batch8 仅改变出现次数(合并)。**clear 非 batch8 新增铁证**。 - -**设计契约矛盾确认(跨批遗留,非 batch8)**:`commands.rs:1672-1675` switchConversation `retain(|_, a| a.conversation_id.as_deref() != Some(&conversation_id))` 注释明说「防 init 重建的内存 HashMap 被清空,重启恢复链路:restore → switchConversation(此处不清目标)→ ai_pending_tool_calls → ai_approve」——该恢复链前提是 restore 重建的 pending 在 switchConversation 时仍在内存,但 L0 clear(批2 起)在冷启动 AiChat onMounted 时就触发(早于用户手动 switchConversation)抹掉 restore 成果。**矛盾真实存在,但归属批2+,非 batch8**。 - -**为何另一位审查 agent 误判 batch8 引入**:`git show 6ad4ec2 -- lib.rs` 只看 batch8 diff 内的 `+ session.pending_approvals.clear()`(那是合并后上提的外层一处),**未对比批2 版本(5c15b72)已有两处 clear**,误判为新增。印证 memory [[code-review-anti-contamination]]:对抗核验须跨版本 git diff,不能只看单 commit diff 的 +/- 行。 - -**⚪ WATCH-1(AiCompleted 补偿前端「各归各复位」描述精度,low)**:lib.rs:74 注释 + prompt 声称「前端 useAiEvents.ts:133-140 按 conversation_id 路由各归各复位」,实际 useAiEvents.ts:133-140 `isCurrent=false`(非当前展示 conv)收到 AiCompleted 时清的是**全局单值 `state.generatingConvId=null`**(:136),非按 conv 各清。因 devflow 前端 generatingConvId 是单值(同时刻只展示一对话),多 conv 后台跑 loop 切走后完成时清全局 null 无副作用。**补偿事件设计意图(防 HMR 残留 generating 卡死)达成**,描述略宽泛。建议注释改为「前端按 conversation_id 过滤,非当前 conv 收到 AiCompleted 清全局 generatingConvId(单值)」。非阻塞。 - -**🟡 WATCH-2(L0 clear 致冷启动审批丢失·跨批遗留·非 batch8)**:L0 `lib.rs:62 session.pending_approvals.clear()` 无条件清空,抹掉 `state.rs:340 restore_pending_approvals` 重建的 pending → 冷启动后 ai_pending_tool_calls(内存数据源)返空 → 待审批 toolCard 不显示。**归属批2+(5c15b72 起两分支无条件 clear),非 batch8 引入**。batch8 仅合并 clear 位置(行为等价)。修复方向(回填 todo,供主代/用户评估,**不属 batch8 审查范围必修**): - - **方案 A(推荐)**:`lib.rs:62 clear()` → `retain(|_, a| !a.recovered)`。仅清非 recovered(本次会话产生的死 pending / HMR 残留),保留 restore 重建(`PendingApproval.recovered=true`,audit.rs:331)。对齐 switchConversation 保护意图,HMR 清死 pending 与冷启动保 restore 两全。 - - **方案 B**:删 `lib.rs:62`。pending 清理由 switchConversation retain / delete_conversation retain / ai_chat_clear 各路径精确管理。 - - **判定**:此 🟡 是**跨批遗留问题曝光**(批2 起就存在),batch8 未恶化(行为等价)。是否修属产品决策(冷启动审批恢复是否是承诺功能),非 batch8 审查必修项。 - -**⚠️ batch5 中间态说明(审查范围外记录)**: -- 本次审查**全程未跑 cargo**(memory [[review-batching-worktree-transient]]:F-09 batch5 agent 正在改 agentic.rs/state.rs,跑 cargo check 抓中间态致误报)。源码形态核验 + git diff 跨版本时序对抗 > check 快照。主代兜底 cargo check --workspace EXIT 0 + test 98 passed 为稳定态报告(本审查采信但不复跑)。 - -- **待修项回流 todo**: **无** 🔴 项(batch8 本身 PASS 无回归) · 🟡 **1 项跨批遗留**(BUG-260619-06-cross-batch L0 clear 致冷启动审批丢失·**归属批2+ 非 batch8**·方案 A `retain(!recovered)` 推荐·供主代/用户评估是否修,非 batch8 审查必修) - -### CR-260619-07 F-09 batch4 启用多会话上线(决策e真并发·前后端·agent f09-batch4·commit d899c58) — ✅ 已审(PASS·⚪2·巡检 2026-06-19 独立 grep/read 核验) - -- **范围**(前后端大改,F-09 上线):IPC 签名加 conv_id(ai_is_generating/ai_chat_send/ai_chat_force_send/ai_chat_stop + 内部移除 active 一致性校验)+ 删 switch readonly(决策 e)+ 删 ai_conversation_create 强制结束旧 loop + 删双写桥接/顶层字段(messages/generating/stop_flag/notify/iteration_used/agent_language/model_override/session_trust 全删,per_conv 唯一真相源)+ 删双写代码(全模块)+ pending_approvals retain 目标 conv + finalize_pending_placeholders 按 conv_id + 前端(api/ai.ts+useAiSend/useAiWindow)传 conv_id。保留 session_state(SW 预留标 allow)+ readonly(前端不读)。 -- **审查要点**:① IPC 签名 conv_id 前端传参(api/ai.ts/useAiSend/useAiWindow);② 删 readonly 后 switch 生成中可切(决策 e)+ 后台 conv 跳过 reload;③ 顶层字段全删(per_conv 唽一)+ 双写清理完整(grep 顶层引用 0);④ conv_id 上下文一致(前端 activeConversationId);⑤ pending_approvals retain 目标 conv(不误杀他 conv);⑥ switch reload 边界;⑦ 保留项(session_state/readonly)合理(零调用方预留按用户指导保留)。 -- **验证(主代兜底)**:cargo check --workspace 0 + vue-tsc 0 + test 98 + grep 顶层删除/per_conv 唯一印证。 -- **⚠️ 双会话回归留用户验收**:开 A 跑→切 B 发→A 后台不退出不污染 B;审批/max 续跑跨会话。后端 per_conv 隔离 + 事件 conversation_id 路由保障。 -- **关联**:F-09 完整(batch1-8+batch4 上线)。 - -**复审结论(2026-06-19·巡检独立 grep/read 核验源码当前形态 + 决策e 真并发对抗分析·commit d899c58)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 - -**7 审查要点逐项核验(file:line 佐证)**: - -| # | 要点 | 佐证 | 判定 | -|---|---|---|---| -| ① | IPC conv_id 前端传参 | `api/ai.ts`:sendMessage/forceSend/regenerate/edit/continue/stop/ai_is_generating 全加 conversationId;`useAiSend.ts`:doSend :125/127 传 `state.activeConversationId`、regenerate :174-183/edit :244-252 有 `if(!convId)` null 保护 | ✅ | -| ② | 删 readonly 后台 conv 可切 | `agentic.rs:546` loop 退出判据 `!per_conv.contains_key(&conv_id)`(conv 存在性,非 active 一致性);:539-541 注释「旧 loop 跑自己 conv 不污染他人,active 切换不让退出」 | ✅ | -| ③ | 顶层字段全删 + 双写清理 | grep `session.generating/messages/stop_flag/notify/iteration_used/agent_language/model_override/session_trust` 全命中**注释**(agentic.rs:400/993、mod.rs:474 等),无实际顶层读写代码(cargo check 0 印证) | ✅ | -| ④ | conv_id 上下文一致 | `run_agentic_loop:338` conv_id 入参快照,全程 `session.conv(&conv_id)`;`try_continue_agent_loop:1075` 显式 conv_id 入参(:1214 注释「active_conversation_id 字段移除,改入参 conv_id」);loop 内 grep active_conversation_id **无实际读写** | ✅ | -| ⑤ | pending_approvals retain 目标 conv | switchConversation(`mod.rs:482`)/deleteConversation(:526)/ai_chat_clear(`chat.rs:536`)/ai_chat_stop(:895/1015)全 `retain(... != target)`,不误杀他 conv,保护 restore 重建链路(注释 :480-481) | ✅ | -| ⑥ | conv_id=null fallback | `ai_is_generating:178-182`(null→fallback active→空返 false)/`ai_chat_send:218-233`(null→active→懒创建 new_id)/`ai_chat_stop:994-998`(空返 Ok) | ✅ | -| ⑦ | ai_continue/stop_loop 移除 active 校验 | `chat.rs:1094/1129` 实际只 `conv_read(&conv_id).generating` 校验,**无 active 一致性**(决策e 落地) | ✅ | - -**对抗核验印证(防污染)**: -- **CR-06 修复延续 ✅(关键)**:`lib.rs:62 session.pending_approvals.retain(|_,a| !a.recovered)` 在 batch4 后**仍保留**(:59-62 注释「BUG-260619-06 修复」)。batch4 删顶层 generating 双写复位(:45)+ 删 active 兜底(顶层退役后无意义),但 retain 修复完整保留 → 上轮审 CR-06 的 🔴 未被新批破坏。 -- **决策e 真并发核心 ✅**:loop 退出判据 conv 存在性(:546)+ loop conv_id 快照不读 active(:348/368)+ guard/stop_flag/notify/messages 全 per_conv(:97/1100/1135)+ 事件 emit 全带 conversation_id → A 后台 loop 切 B 不退出不污染,设计契约与 switchConversation retain 一致。 -- 主代自审 PASS + cargo check 0 + test 98,独立核验源码形态印证(非信声明)。决策e 真并发时序保障经多维度交叉(前端传参/后端 fallback/loop 快照/退出判据/retain 路由)成立。 - -**⚪ low(2)**: -1. **注释过时(6 处,功能正确但误导维护)**:`agentic.rs:400`(model_override「入参←session」→ 实为函数入参)/`:993`(session.iteration_used→ 实为 per_conv)/`chat.rs:1079`(session.iteration_used→per_conv)/`:1082-1083`&`:1117-1118`(ai_continue/stop_loop 文档注释「校验 active 一致性」→ batch4 已移除)/`mod.rs:474`(批2 迁移说明残留)。建议批量更新对齐 batch4 per_conv 化 + 移除 active 校验。 -2. **双会话实机验收(CR-07 :883 已声明)**:开 A 跑→切 B 发→A 后台不退出不污染 B;审批/max 续跑跨会话。后端 per_conv 隔离 + 事件 conversation_id 路由 + loop 退出判据 conv 存在性已核验保障,真并发时序(两 conv 同时 loop + provider pool/llm_concurrency 竞争)需实机验收。 - -- **待修项回流 todo**: **无** 🔴/🟡 项 - ---- - -### CR-260619-08 df-ai 会话意图识别层 intent.rs 新建(纯函数模块·不接入 loop·commit 7724cb7) — ✅ 已审(PASS·⚪1 low·独立 grep/read 核验) - -- **结论(2026-06-19·独立 grep/read 核验 commit 7724cb7)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 -- **范围**:`crates/df-ai/src/intent.rs`(新建 682 行)+ `crates/df-ai/src/lib.rs`(`pub mod intent;` 注册 4 行)。 - -**6 维度逐项核验(file:line 佐证 + 判定)**: - -| 维度 | 核验点 | 佐证 | 判定 | -|---|---|---|---| -| ① 识别规则 | 优先级取舍 + 置信度封顶 + 求和 | `recognize:289-301` 依次 best_in_group(SPECIFIC→ENTITY→GENERIC) 命中即 return 不向下累积 · `best_in_group:305-329` 组内 score=Σ命中权重 `:315` + `.min(1.0):322` 封顶 · 平局 `b>=conf 保留旧:324`(SPECIFIC_GROUP 数组 Code 首位 :200 同分 Code 胜) | ✅ | -| ② 工具名对齐 | ToolDomain 29 工具名逐条 vs tool_registry.rs | 自动化 perl 比对:registry 29 == intent 29,**in registry but NOT in intent: (none) / in intent but NOT in registry: (none)** · Data 18(list_projects..get_task_count)+File 10(read_file..rename_file)+Http 1(http_request)与 `tool_registry.rs:1838-1852` 基线测试 expected 完全一致 | ✅ | -| ③ ModelTier None 预留 | 恒 None,未误接 provider/model | `suggested_model_tier:376-379` body 仅 `None` + TODO 注释 · ModelTier 枚举 :80-87 仅 Fast/Standard/Heavy 定义,无 provider/model 关联 · 单测 :653-671 遍历 11 意图全断言 None | ✅ | -| ④ 独立性 | 不接入 loop/不读 registry/不碰 src-tauri | `git show --stat 7724cb7` 仅 2 文件(intent.rs 新增 + lib.rs +4)·lib.rs:12 仅 `pub mod intent;` · intent.rs grep 无 `use crate::`/`commands`/`tool_registry`/`agentic` 运行期依赖(仅文档注释 :6/:15-16/:222-223 提及)·无 src-tauri 改动 | ✅ | -| ⑤ 测试覆盖 | 36 单测覆盖关键路径 | `cargo test -p df-ai --lib intent::` **36 passed 0 failed**(EXIT 0 复跑印证无中间态漂移)·覆盖:枚举 as_str / recognize 中英文 / 优先级 SPECIFIC>ENTITY>GENERIC / 边界空串+空白+无关键词 / 置信度封顶 / tool_subset 各 domain + fallback 空 / 跨 domain 去重 / ModelTier None / Default 构造 | ✅ | -| ⑥ 设计文档对齐 | 方式 A 规则识别 | `意图识别层论证-2026-06-19.md` 第 11 条「方式 A 规则(零延迟零成本)落 intent.rs + tool domain 标签」+ 第 58 条「子集扩充非裁剪 / None fallback 全量零回归」+ 第 13 条「loop 入口生效一次不进 loop 体」全对齐实现 · 文档预估 ~200 行,实现纯逻辑约 380 行(682 含 36 单测 ~300 行)量级合理 | ✅ | - -**对抗核验印证**: -- **维度① 平局处理**:recognize_debug_zh 测试输入「bug 复现 调试 排查」(bug→Code=1.0 / 复现0.9+调试1.0+排查0.9=2.8→1.0→Debug)同分 1.0,Code 在 SPECIFIC_GROUP 首位先遍历,`b>=conf`(1.0>=1.0)保留 Code → 返 Code ✅ 测试 :420 断言 Code 正确反映此行为 -- **维度② 维度声明文档笔误**:登记项写「Data 18/File 10/Http 1」(暗示 28)但实际 18+10+1=**29**。tool_registry.rs:1830 基线测试断言 `29(18 data + 10 file + 1 http)`。intent.rs 正确对齐 29,非 28。**声明笔误不影响代码正确性**,仅文档表述 -- **关键词歧义点(规则识别固有局限,非 bug)**:"修改"(File 0.7)+ "项目"(Project 1.0)同命中 ENTITY 组,File 在 ENTITY_GROUP 首位先遍历 → "修改项目名称"会识别为 File 非 Project。方式 A 准确率 70%+(设计文档第 27 条声明),子集扩充非裁剪 + None fallback 零回归兜底,非 high/med -- **测试用例歧义容忍**:recognize_search_zh :454-461 输入「搜索 代码 grep」(Search 搜索1.0+grep1.0=2.0→1.0 vs Code 代码1.0→1.0 同分,Code 首位胜 → 返 Code),用 `matches!(Search|Code):460` 容忍已知歧义 ✅ 测试合理(见 low-1) - -**⚪ low(可选·非必修)**: -1. **recognize_search_zh 测试注释与用例自相矛盾**(`intent.rs:457-459`):注释说「调整用例避免歧义」但用例本身仍触发歧义(仅靠 `matches!(Search|Code)` 容忍)。建议要么改输入为纯 Search 关键词(如 "grep 查找" 已有 recognize_search_pure :464 覆盖,本用例冗余),要么删注释「调整用例」表述保留 `matches!` 容忍说明。纯测试可读性,非功能。 - -- **待修项回流 todo**: **无** 🔴/🟡 项 - -### CR-260619-09 F-260619-03 文件访问权限模型 Phase B+C + anthropic_compat 连续user合并(后端 state/audit/chat/conversation/mod/lib + 前端 DirAuthDialog 等 15 文件·commit bddbfd4 + 7c98134) — ✅ 已审(PASS·🟡1⚪2) - -- **结论(2026-06-19·独立 grep/read 核验 commit bddbfd4+7c98134)**: ✅ **PASS** — 🔴0 🟡1 ⚪2 -- **验证**: cargo test -p devflow --lib state:: **15 passed 0 failed**(Phase A/B/C 全覆盖) / cargo check -p devflow **EXIT 0 无 warning** / vue-tsc --noEmit **EXIT 0**。独立 grep 核验 6 维度源码当前形态,不信 agent 自报。 - -**6 维度逐项核验**: - -| # | 维度 | 判定 | -|---|------|------| -| 1 | 权限模型正确性 | ✅ session 进程级全局(`Arc>` state.rs:305),clear 时机完整(conversation.rs create:74/switch:151/delete:230);NeedsAuth 挂起恢复链路完整(pending_count+=1 audit/mod.rs:283 + 占位 tool_result :299 + emit AiDirAuthRequired :300 + ai_authorize_dir→try_continue chat.rs:628) | -| 2 | 黑名单完整性 | ✅ Win(System32/SysWOW64/System/Program Files×2) + Unix(/etc /usr /bin /sbin /boot /dev /proc /sys)覆盖合理;分段精确匹配防误伤(test_blacklist_no_false_positive 印证 "my program files backup" 不拒);黑名单优先于白名单双判(is_authorized state.rs:353 + check_path_authorization :441 独立判);**write_file 新建路径三层防护**:预校验 check_file_tool_auth(audit/mod.rs:253) + handler 词法层 is_authorized(tool_registry.rs:326) + handler canonicalize 层 is_authorized(:334),不存在路径也判黑名单,不绕过 | -| 3 | 写约束覆盖 | ✅ tool_registry.rs 逐条核验:delete_file=High(:1438) / write_file=Medium(:1061) / patch_file=Medium(:1175) / append_file=Medium(:1401) / rename_file=Medium(:1507);读类 read_file/list_directory/file_info/search_files=Low。路径授权放行后**仍走 RiskLevel 审批**(正交性:check_file_tool_auth 在 risk_level 分类**前**调 audit/mod.rs:233-259,Authorized drafts 才进下方 Low/Med/High 循环 :325) | -| 4 | 挂起恢复链路 | ✅ ai_authorize_dir(chat.rs:520)三分支完整:deny→Err+恢复 loop(:542) / once→add_session_allowed_dir(:575) / always→add_persistent_allowed_dir(:580,失败降级 session);复用 ai_approve 执行链(run_workflow 特殊处理 :589);**path_auth 守卫双向严密**:ai_approve :362 拦 path_auth 挂起回滚 pending+Err,ai_authorize_dir :534 拦普通审批 ok_or_else Err | -| 5 | 前端弹窗交互 | ✅ DirAuthDialog.vue 三选项(once/always/deny)+ 双重守卫(pendingDirAuth + isViewGenerating :55)+ dirAuthActing 防重入(:54);useAiEvents.ts pendingDirAuth 置位(:231)+ 三处清空(AiApprovalResult:335/AiCompleted:357/AiError:407);NO_RESET_WATCHDOG 含 AiDirAuthRequired(:47)不触发整流超时 | -| 6 | 跨会话隔离 | ✅ clear_session_allowed_dirs(state.rs:673)仅清 allowed_dirs.session,不动 per_conv/pending_approvals;conversation.rs create/switch/delete 的 retain 仅清目标 conv 的 pending;**session 全局单例不构成 F-09 回归**(Phase B 沿用 active 单全局模型,agent 风险点5 已承认,真多会话独立临时授权需迁 PerConvState 属后续工作) | - -**附带 7c98134 merge_consecutive_users 核验**: anthropic_compat.rs:265 合并相邻 user 块为一条 user 含 [tool_result..., text] blocks 数组,String/Array content 双形态规范化(:282-294),while 循环不增 i 续合并多连续 user(:297),逻辑正确,打破 GLM 1214 连续 user 恶性循环。 - -**🟡 MED-1**: `state.rs:673` + `audit/mod.rs:245` + `tool_registry.rs:1065` — 预校验(process_tool_calls)与 handler 闭包两端**独立 read lock** 取 allowed_dirs.session 快照,并发 clear_session(create/switch/delete)下存在"预校验放行→handler 拒绝"窄窗口不一致。mod.rs:251 注释声称"两端授权判定一致"未标注并发限制。后果仅工具返 Err(LLM 收错误自行调整,非数据破坏),触发条件极窄(用户在工具执行瞬间切会话+该会话有 session 临时授权目录)。建议:注释补充"并发 clear_session 下两端快照可能不一致,后果为工具 Err 非数据破坏"说明,或后续 session 字段迁 PerConvState 时顺带消除。 - -**⚪ LOW-1**: `state.rs:412` is_in_system_blacklist Unix 分支未覆盖 `/var`(日志/spool/cron)和 `/root`(root 家目录)。影响有限:黑名单是用户误授权兜底,/var/root 非系统核心不可替换目录,且需用户手动授权才触达。后续可按需补充。 - -**⚪ LOW-2**: `state.rs:327` AllowedDirs.session 进程级全局单例,F-09 多会话并发下各会话无法独立临时授权(切走即清,切回需重新授权)。agent 实施报告风险点5 已明确承认此限制,Phase B 对齐 active 单全局模型,非新回归。真多会话独立临时授权需迁 PerConvState.allowed_dirs,属后续工作。 - -- **待修项回流 todo**: **无** 🔴/🟡 项(MED-1 为观察级注释补充,非阻塞性代码修复;LOW 两项为已知限制/可选扩展) - ---- - -### CR-260619-10 消息级溯源 P0 地基(ChatMessage.id + ai_messages 拆表 + V21 迁移 + AiMessageRepo + audit message_id 列·commit e981c14) — ✅ 已审(PASS) - -- **结论(2026-06-19·独立 grep/read 核验 commit e981c14 + cargo test 复跑)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 -- **验证**: `cargo test -p df-storage` 45 单测 + 11 集成全 passed(V21 5 测:新库空跑/老库迁移/坏数据/幂等重跑/列补建 + message_repo 5 测全绿,无漂移)。git show e981c14 10 文件 diff 逐文件核验。 - -**5 维度逐项核验**: - -| # | 维度 | 判定 | -|---|------|------| -| 1 | 迁移正确性(migrate_v21 migrations.rs:374) | ✅ 幂等三重防护(COUNT 探测 :400-407 / INSERT OR IGNORE :472/:489/:505 / schema_version PK IGNORE)+ 坏数据 warn+continue :432-435 + 全量逐条 :438-483 + 新库空跑(V9_SQL :727-744 同步建 ai_messages 表 + message_id 列)+ msg_migrated_{conv_id}_{seq} :442 填 ai_messages.id 正确(V21 测 v21_legacy_db_migrates_messages 覆盖老库迁移 + 字段提取 + seq 递增) | -| 2 | id 格式(new_message_id types.rs:134) | ✅ AtomicU64 COUNTER + ts 并发安全 + 时钟回拨单调(计数器兜底)+ serde `#[serde(default)]` 老 JSON → None round-trip + 6 构造器(provider.rs:45 system/48 user/51 assistant/54 assistant_with_tools/57 tool_result/63 user_parts)全覆盖 `id: Some(new_message_id())` | -| 3 | AiMessageRepo(message_repo.rs) | ✅ 5 方法 SQL/参数/返回正确:insert_batch INSERT OR IGNORE 幂等 :80 / list_by_conversation ORDER BY seq :115 / delete_range 左闭右开 + None 末尾 :145-153 / update_status :179 / update_content_by_tool_call_id 定点 :207。baseline_tests(mod.rs:263)断言 ai_messages 白名单 None 合理(专用 Repo 不进通用 query 路径) | -| 4 | P0 边界(未误接 P1/P2) | ✅ AiMessageRepo 仅 df-storage 自身注册 + test,**0 外部调用方**(grep crates/src-tauri 业务侧 0 命中)/ ContextManager 读写路径未改 / save_conversation 未改双写 / audit message_id None 占位仅 finalize.rs:40 单点(P1 接真值) | -| 5 | 迁移耦合点(types + migrations) | ✅ types.rs:75-77 ChatMessage 定义注释「改字段名必须同步更新 migrate_v21」+ migrations.rs:370-373 反向标注「同步标注已在 types.rs 加注释」,双向闭环 | - -**主代核查·审查防污染印证**: -- 迁移数据完整性逻辑独立走查:messages JSON → Vec 逐条 :430,seq `messages.iter().enumerate()` :438(0 起递增),status 归一化 None/空→"active" :458-461(列 NOT NULL DEFAULT 'active' 对齐),created_at timestamp 优先 fallback conv_created_at :466-469。 -- audit 14 处 message_id 说法:范围描述措辞,实际 grep src-tauri 仅 finalize.rs:40 单点构造 AiToolExecutionRecord(AiToolExecutionRecord { 仅 1 处),14 处指未来调用覆盖面。单点 None 占位符合 P0 边界(列已建、值待 P1),非缺陷。 -- workspace 自验 EXIT 0(commit message 自报)+ 本会话 cargo test 45+11 复跑印证无漂移(memory [[review-batching-worktree-transient]] P0 与 MCP 并行警惕快照漂移,以实际磁盘内容为准已落实)。 - -- **⚪ WATCH-1**: V21_SQL(migrations.rs:498)与 V9_SQL(migrations.rs:727)两处 ai_messages 建表 SQL 镜像,改动须两边同步——已在两处加注释 :496-497 / :724-726 标注,后续加列/改约束需双向维护(低风险,有注释护栏) -- **待修项回流 todo**: **无** 🔴/🟡 项 - -### CR-260619-11 MCP Server(df-mcp crate + stdio 协议 + CLI 子命令·commit f217e39) — ✅ 已审(PASS·⚪2) - -- **结论(2026-06-19·独立 grep/read 核验 commit f217e39 + cargo test df-mcp 8 passed 复跑)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 -- **验证**: cargo test -p df-mcp 8 passed 0 failed 复跑确认(initialize/tools_list 高风险过滤/read_only/路由/High 拒/create+list 往返/visible 谓词全过)。HEAD vs f217e39 在 df-mcp 范围零 diff,源码形态即 commit 形态。19 工具逐条核验 vs tool_registry + df-storage Repo 方法全部存在。 - -**6 维度逐一核验**: - -| # | 维度 | 判定 | -|---|------|------| -| 1 | JSON-RPC 协议正确性 | ✅ protocol.rs:109-127 路由 initialize/initialized/tools/list/tools/call/ping 5 method + 错误码常量 -32700/-32600/-32601/-32602/-32603 齐全。server.rs:60-106 stdin 逐行 read_line + write_response 单行 \n framing 正确。Notification(id=None)不回(:92-102)。initialize 返 serverInfo+capabilities.tools(:117-130)。tools/list 返 `{tools:[{name,description,inputSchema}]}` 符合 MCP Tool schema(:136-143)。tools/call 返 `content:[{type:"text"}]` + isError 区分业务错/协议错(protocol.rs:185-201) | -| 2 | **安全降级三层严密性(本 CR 重点)** | ✅ **严密无漏** — 层①tools/list visible 谓词(server.rs:183-189:`risk!=High` 非ro / `risk==Low` ro)过滤 4 个 High(delete_project/delete_task/delete_idea/run_workflow)永不暴露。层②dispatch 兜底(server.rs:163 `if spec.risk==RiskLevel::High` 拒)+ read-only 下 Medium 也拒(:155 `spec.risk!=RiskLevel::Low`)。层③handler 二次拒(tools.rs:305/489/590/651 四个 High handler 体内无条件 return error,连 ctx/args 都不读)。**对抗核验**:purge_project df-mcp 未注册(物理删比软删更危险,数据层 MCP 故意只暴露 delete_project 软删,设计正确非遗漏);方法路由仅 5 个无其他 method 触发工具;文件工具(read/write/patch/list/search/command)0 命中。测试 tools_call_high_risk_is_rejected + tools_call_read_only_rejects_medium + tools_list_excludes_high_risk_by_default 全覆盖 | -| 3 | 不暴露文件系统 | ✅ grep `read_file\|write_file\|patch_file\|list_directory\|search_files\|run_command\|delete_file\|rename_file\|append_file\|http_request` 全 df-mcp/src **0 命中**。19 工具全为数据层(项目/任务/灵感/工作流/回收站),防绕过应用内路径校验 | -| 4 | 工具复用正确性 | ✅ 19 工具逐条核验:list_projects/get_project/create_project/update_project/delete_project/bind_directory/list_tasks/create_task/update_task/advance_task/delete_task/list_ideas/create_idea/update_idea/delete_idea/evaluate_idea/run_workflow/list_trash/restore_project。复用 df-storage Repo(ProjectRepo/TaskRepo/IdeaRepo)零重复 CRUD。advance_task 走 advance_status_atomic CAS(task_repo.rs:151 WHERE id AND status=expected + deleted_at IS NULL 兜底)。bind_directory 走 find_path_conflict(project_repo.rs:190)+ df-mcp 自实现 normalize_path(tools.rs:695 与 df-project scan.rs:24 逐字一致)。evaluate_idea 本地启发式(tools.rs:623 纯函数,不调 LLM) | -| 5 | CLI 子命令 | ✅ main.rs:16 首参 == "mcp-server" 路由 + run_mcp_server(:28)解析 --db/--read-only/--print-config/-h/--help。default_db_path(:112)Windows %APPDATA%/devflow/devflow[-dev].db(cfg!(debug_assertions)→devflow-dev.db,release→devflow.db)与 GUI 同库。--print-config 输出 `{"mcpServers":{"devflow":{"command":"devflow","args":["mcp-server"[,"--read-only"]]}}}` (:66-70)。windows_subsystem="windows" 保留(main.rs:2) | -| 6 | 并发安全 | ✅ db.rs:22 `PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;` 确认启用(run_server server.rs:38 复用 Database::open)。仅 stdio/本地无认证,依赖本地隔离(Tauri 桌面应用非网络服务,门槛可接受,与 tool_registry Path Traversal 同评估口径 CR-260618-11 #8) | - -**⚪ 可选优化 (2)**: - -| # | file:line | 问题 | 建议 | -|---|-----------|------|------| -| 1 | `tools.rs:664-665` | `list_trash` 用 `unwrap_or_default()` 吞 DB 错误返空列表。MCP 对外暴露,DB 故障时客户端收 `{projects:[],tasks:[],...count:0}` 误判「回收站为空」(与 CR-260618-13 SMELL-P0-1 同款降级模式,但 MCP 对外场景误导性更高) | 改 `match { Ok(v)→v, Err(e)→return err_str(e) }` 让客户端感知 DB 故障 | -| 2 | `tools.rs:616` | `evaluate_idea` `let _ = repo.update_full(&rec).await;` 丢弃写回结果。scores 写回失败时客户端仍收旧 idea + 新 scores(响应用写回前的 idea clone :617)轻微数据不一致 | 写回失败时 err_str 返回,或响应标注「scores 未持久化」(只读评估场景影响小,可接受现状) | - -**主代核验印证(防污染)**: -- agent 自报 8 passed,主代独立 cargo test -p df-mcp 复跑 8 passed 0 failed 一致 ✅ -- 登记项维度2 把 purge_project 列为 High 风险工具之一,独立 grep 核验 df-mcp **未注册 purge_project**(仅 delete_project 软删),描述偏差非 df-mcp 遗漏 — df-mcp 设计正确(数据层不暴露物理删,tool_registry.rs 有 purge_project 但 MCP 不暴露) -- 三层安全降级源码佐证齐:visible 谓词 server.rs:183 / dispatch 兜底 server.rs:163 / handler 二次拒 tools.rs:305+489+590+651,四 High 工具三层全覆盖 - -- **待修项回流 todo**: **无** 🔴/🟡 项(⚪2 可选:list_trash 吞 DB 错误返空列表对外误导 / evaluate_idea 丢弃写回结果,非阻塞) - ---- - -### CR-260619-12 消息级溯源 P1 + 流式失败重试修复(context/audit/知识/knowledge_timeline/chat/useAiEvents·commit d3e7640) — ✅ 已审(PASS·🟡1⚪1) - -- **结论(2026-06-19·独立 grep/read 核验 commit d3e7640 源码当前形态 + cargo test --workspace 复跑)**: ✅ **PASS** — 🔴0 🟡1 ⚪1 -- **验证**: 独立 grep/read 核验 7 文件源码当前形态(diff 快照对比) / `cargo test -p df-ai context::tests::last_` 4 测全绿(last_assistant_message_id_returns_latest / last_user_message_id_returns_latest / last_message_id_none_when_no_such_role / last_message_id_none_for_legacy_no_id) / `cargo test --workspace` 全 11 crate 0 failed(df-ai 139 passed 含 4 P1 新测 / df-workflow 23 / 其余全 ok)。 - -**5 维度逐项核验**(源码当前形态为准,非 diff 快照): - -| # | 维度 | 判定 | -|---|------|------| -| 1 | P1 message_id 传递完整性 | ✅ audit/mod.rs 6 处 audit_tool_call 全传 current_message_id(:283 rejected / :319 pending / :396 auto_dedup / :429 pending / :483 auto_trust / :536 auto,独立 grep 确认无遗漏·cache.rs:106 仅注释引用非调用) / process_tool_calls :224-227 取 `session.conv_read(conv_id).messages.last_assistant_message_id()`,agentic/mod.rs:882 assistant_with_tools 已 push 后 :913 才调 process_tool_calls,取到的就是本轮 assistant id / 知识提炼 knowledge_inject.rs:355-361 反向扫描 messages 取末条 assistant id / 4 处 build_knowledge_context(chat.rs:151/317/951/1115)全传 user_message_id.as_deref() / 对抗性核验:新构造 ChatMessage(provider.rs:47/50/53 user/assistant/assistant_with_tools 全 `id: Some(new_message_id())`)非 None,**无 None 泄漏到已处理 assistant** ✅ | -| 2 | 老数据兼容 | ✅ ChatMessage.id=None(老反序列化)→ last_assistant_msg_id/last_user_message_id 返 None(context.rs:483 `and_then(\|t\| t.message.id.clone())` 直接透传 None)→ source_ref 降级 conv:{conv_id}(knowledge_inject.rs:455-458 match / knowledge_timeline.rs:91-94/108-111 match None 分支),**无 conv_msg:None 无效值** ✅ | -| 3 | 症状1 修复(useAiEvents.ts) | ✅ AiError :401 flushCurrentText 在 :404 `state.currentText=''` 前(顺序正确) / flushCurrentText :134 `last.role==='assistant' && !last.isError` 跳错误气泡(AiStreamRetry :199 push 的 isError 气泡) / AiError 错误气泡 :417 在 flush 之后 push(不冲突) / **对抗性场景核验**:AiStreamRetry→AiError 链路下末条是 isError 气泡,flush 跳过不覆盖错误提示 ✅ / AiAgentRound/AiCompleted 正常路径末条非 isError,`!last.isError` 仍 true 正常回填,无副作用 ✅ | -| 4 | 症状2 修复(ai_regenerate) | ✅ chat.rs:109-124 `pop_last_assistant_round` 返 false 时取 `all_messages_clone().last()` 判 `MessageRole::User`,last_is_user=true 不报错直接走重跑路径(末尾 user 等价重发) / false 仅在空对话/末尾非 user 才 `conv.generating=false; return Err("没有可重新生成的回复")` ✅ | -| 5 | 重试不丢参数 | ✅ ai_regenerate :172 spawn `run_agentic_loop(...)` 入口读 `session.conv(&conv_id).messages` 完整历史(agentic/mod.rs:570/668 全量 messages 迭代),不丢历史参数 ✅ | - -**🟡 MED-1**(`src/composables/ai/useAiEvents.ts:401`): AiStreamRetry→AiError 失败重试链路下部分回复仍可能丢。场景:流式失败→AiStreamRetry push/更新 isError 气泡(末条变 isError)→重试耗尽→AiError flushCurrentText 取末条(isError 气泡)被 `!last.isError` 跳过,**部分回复不会回填到占位 assistant 气泡**(占位气泡 content 仍为空),随后 :404 `state.currentText=''` 清空,本轮部分回复丢失。纯 AiError(无 AiStreamRetry 前导)路径完全有效,仅重试耗尽链路存在覆盖盲区。建议:flushCurrentText 跳过 isError 时改为回填到「isError 气泡之前的最近非 isError assistant 气泡」,或 AiError 收尾前显式查找占位气泡回填。 - -**⚪ LOW-1**(`src-tauri/src/commands/ai/knowledge_inject.rs:355`): 知识提炼 source_ref 取「对话最新 assistant」(反向扫描全量 messages,非提炼窗口 recent 6 条内的最新)。OnComplete 异步 spawn 时序下,若 OnComplete 触发后用户又发新消息(虽 OnComplete 通常即时触发),last_assistant_msg_id 可能指向非真正产出该条知识的 assistant。精度边界非正确性问题,source_ref 仍指向对话内合法 assistant,展示侧兼容。注释已说明设计选择(「确保取到对话最新 assistant,即便它不在提炼窗口内也属于本轮 AI 产出」)。 - -**审查防污染印证**: -- audit 6 处调用方计数:独立 grep audit_tool_call 命中 finalize.rs:22 定义 + mod.rs 6 调用(:283/319/396/429/483/536)+ cache.rs 注释引用。**与登记项「6 处」一致**,无遗漏无多算。 -- chat.rs build_knowledge_context 调用方计数:独立 grep 命中 4 调用(151/317/951/1115)+ 1 定义(knowledge_inject.rs:200)。**与登记项「4 处」一致**。 -- assistant push 时序:注释声称「agentic.rs:882 已 push」,独立 Read agentic/mod.rs 确认 :882 `session.conv(&conv_id).messages.push(msg)` 在 :908 has_tool_calls 判断 + :913 process_tool_calls 之前,**取到的 last_assistant_message_id 就是刚 push 的本轮 assistant**(provider.rs:53 assistant_with_tools 必生成 id 非 None),对抗性核验成立,无 None 泄漏。 -- cargo test --workspace 独立复跑 EXIT 0(非主代自报),11 crate 全 ok,df-ai 139 passed 含 4 P1 新测,印证 memory [[review-batching-worktree-transient]] 教训(源码形态 > check 快照)。 - -- **待修项回流 todo**: **无** 🔴/🟡 项(🟡 MED-1 主代已修:flushCurrentText 改从末尾向前找非 isError 占位 assistant 写入,AiStreamRetry→AiError 链路部分回复不再丢) - -### CR-260619-13 任务查询按状态过滤(list_tasks status·commit 2f8b13f) — ✅ 已审(PASS·⚪1) - -- **结论(2026-06-19·独立 grep/read 核验 commit 2f8b13f)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 -- **核验**:`cargo check -p df-mcp -p devflow --manifest-path src-tauri/Cargo.toml` EXIT 0。逐维度核验源码当前形态(commit 后): - - **维度1 status 过滤正确性** ✅:tool_registry.rs:620-622 `args.get("status").and_then(|v| v.as_str())` → `tasks.retain(|t| t.status == status)`。TaskRecord.status: String(models.rs:59),`String == &str` 通过 PartialEq 成立;status 不传时跳过 retain(可选)语义正确。df-mcp tools.rs:357-359 `list.retain(|t| t.status == *st)` 同理。 - - **维度2 7 态完整** ✅:task_advance_node.rs:147 spec enum `["todo","in_progress","in_review","testing","done","blocked","cancelled"]` 与 commit 文案 7 态逐字一致。filter 纯 String 相等不校验合法性(未知 status 返空集,无 crash/panic),行为可接受。 - - **维度3 df-mcp 一致** ⚪:handler 功能就绪(tools.rs:349,357-359),**但 spec(tools.rs:108)只声明 `project_id`,未声明 `status`** —— MCP 客户端无法从 schema 发现该参数。commit message 已声明「spec 声明待补(中文 Edit 编码限制)」,已知延后。记 ⚪ WATCH-1。 - - **维度4 不破坏现有** ✅:tool_registry project_id 分支(:614-618)+ offset/limit/has_more(:624-627)路径不变;df-mcp project_id_filter + list_active 不变,新增 status 仅追加 retain。注意 df-mcp 原代码 `if let Some(pid) = project_id_filter` 改为 `&project_id_filter`+`*pid`(借用重构),行为等价(project_id_filter 仅借用未移动)。 -- **⚪ WATCH-1**:df-mcp list_tasks spec(tools.rs:108)未声明 status 参数,MCP 客户端 schema 不可见。commit 已声明延后补(中文 Edit 编码限制),后续补 schema 时一并补 status field。 -- **状态**:✅ 已审(PASS) -- **待修项回流 todo**: **无** 🔴/🟡 项 - ---- - -### CR-260619-14 多会话并发前端 UI 阶段3/4(generatingConvs:Set + 侧栏并行 + 独立窗口 per-conv·commit 7d54029) — ✅ 已审(PASS) - -- **结论(2026-06-19·独立 grep/read 核验 commit 7d54029 当前形态,HEAD=7d54029 无后续代码漂移)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 -- **验证**: `npx vue-tsc --noEmit` EXIT 0 独立复跑 ✅ / `grep -rn generatingConvId src/` 仅 2 处注释命中(stores/ai.ts:196/220),零代码读写残留 ✅ / 逐维度源码核验(非 diff 快照)。 -**6 维度逐项核验**(源码当前形态为准): - -| # | 维度 | 判定 | -|---|------|------| -| 1 | Set 响应式 | ✅ stores/ai.ts:70 `generatingConvs: new Set()` 在 `reactive({})` 内;:204-206 `isGenerating` 调 `state.generatingConvs.has(convId)`。Vue 3 reactive 对 Set 的 has/add/delete 提供 collection proxy 跟踪,computed/template 内调用建立响应式依赖,Set 变更触发更新。AiChat(:283)/MessageList(:197/354)/DirAuthDialog(:51)/MaxRoundsCard(:43) 四处 computed 调 `store.isGenerating(activeConversationId)` 均建立依赖,UI 自动刷新 | -| 2 | 5 处迁移完整性 | ✅ useAiEvents.ts:362(Completed) `state.generatingConvs.delete(event.conversation_id \|\| '')` / :415(AiError) 同 delete / :465(路由非当前 conv) `state.generatingConvs.delete(convId \|\| '')` / :472(路由标记) `state.generatingConvs.add(convId)`。外加 useAiWindow.ts:65/196(detach delete + restore add)+ useAiStream.ts:30(clear)+ useAiConversations.ts:78(newConversation clear)。grep 零代码赋值残留 | -| 3 | AiError 精确清 | ✅ useAiEvents.ts:415 `state.generatingConvs.delete(event.conversation_id \|\| '')` — 仅清出错会话,不再 `= null` 全局清空(原单值语义会误清其他并发会话)。多会话并发下 A 报错不影响 B 生成态 | -| 4 | 侧栏并行指示 | ✅ ConversationSidebar.vue 三处独立脉冲点:`store.isGenerating(conv.id)` 在 :50(搜索结果)/:132(活跃分组,带 F-09 注释)/:177(归档分组)。各会话独立判定,多会话同时生成时各自项独立显示 `.ai-conv-gen-dot`。i18n key `aiChat.generating` 中英对称(zh:7 '生成中…' / en:7 'Generating…') | -| 5 | 独立窗口 per-conv | ✅ useAiWindow.ts:28-40 前缀常量 `ai-detached-`/`df-ai-gen-`/`df-ai-text-` + detachedLabel/genKey/textKey 生成器。:46 detachPanel 接受 convId → label per-conv;:54-57 写 per-conv 快照;:101-108 reattachPanel 关闭所有 `ai-detached-*` 前缀窗口(含旧 'ai-detached');:117-128 cleanupAllDetachedSnapshots 清所有 per-conv + 旧单 key | -| 6 | 向后兼容 | ✅ (a) 单会话场景:Set 0/1 元素行为等价(isGenerating has 单元素等单值 null/非null)。(b) :161/179/186/187/219/222 多处读路径 `localStorage.getItem(per-conv) \|\| localStorage.getItem('df-ai-gen')` 双兜底,旧版本写入的单 key 快照仍可读。(c) streaming 单值保留(stores/ai.ts:63)粗粒度"任一会话流式中",供 AiChat 等场景判断当前视图流式态 | - -**审查防污染印证**: -- HEAD = 7d54029,`git diff 7d54029 --stat` 仅 docs/待审查.md +16(本次回填),12 源文件零后续漂移,源码形态可信(memory [[review-batching-worktree-transient]] 教训印证)。 -- AiError 修复为本批最高价值项:原单值 `generatingConvId = null` 在多会话并发下会误清其他会话生成态(侧栏脉冲错灭/ghost),改 `delete(event.conversation_id)` 精确清是核心正确性修复。 -- useAiStream.ts:30 `state.generatingConvs.clear()` 全清(非精确)是合理的兜底设计:整流超时无法确定具体 conv(无 event 携带 convId),保守全清避免幽灵,与 AiCompleted/AiError 精确 delete 互补,非缺陷。 - -- **⚪ WATCH-1**: useAiConversations.ts:78 `newConversation` `generatingConvs.clear()` 全清 — 多会话并发下新建会话会清掉其他后台生成态的侧栏脉冲指示(但不影响后端真实生成,仅前端视觉)。当前设计合理(新建会话视作用户关注转移),若后续报告后台会话脉冲丢失可改精确清。 -- **⚪ WATCH-2**: streaming 单值(stores/ai.ts:63)在多会话并发下语义为"任一会话流式中",各组件用它判当前视图流式态时需配合 `isGenerating(activeConversationId)` 联合判定(4 组件已对齐)。若未来仅读 streaming 单值可能误判其他会话流式影响当前视图,当前 4 处 computed 均联合判定无隐患。 -- **待修项回流 todo**: **无** 🔴/🟡 项 - ---- - -### CR-260619-15 F-260619-01 任务关联灵感(tasks.idea_id 1对1 单向·V20 迁移·commit 4a87c55) — ✅ 已审(PASS·⚪2) - -- **结论(2026-06-19·独立 grep/read 核验 commit 4a87c55 + 复跑 cargo test df-storage 47 passed + vue-tsc EXIT 0)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 -- **7 维度逐项核验**(独立 grep 源码当前形态,不信 commit message): - -| # | 维度 | file:line | 判定 | -|---|------|-----------|------| -| 1 | V20 迁移正确性 | `migrations.rs:361-369`(migrate_v20)+ `:575-580`(V9_SQL 列)+ `:47`(steps 含 (20,migrate_v20))+ `:26`(注释从"预留给 F-260619-01 跳过"更新为"V20 = F-260619-01") | ✅ column_exists 探测跳过 ALTER + ALTER 兜底老库 + V9_SQL 新库直接 `idea_id TEXT REFERENCES ideas(id)` + 写版本 20;steps 数组 20 项 + 注释同步更新。schema_version INSERT 普通(非 IGNORE)与 v1-v19 全一致模式(grep 20 处全 `INSERT INTO schema_version`),run() 正常流程 current_version<20 只调一次无 PK 冲突 | -| 2 | TaskRecord idea_id | `models.rs:78-82`(`pub idea_id: Option` + `#[serde(default)]`) + `task_repo.rs:34`(task_from_row `idea_id: row.get("idea_id")?`) | ✅ Option + serde default 兼容老前端 JSON + from_row 读 | -| 3 | task_repo SQL 完整 | `task_repo.rs:52-58`(insert 加列+占位 ?13)+ `:62-65`(update 加 idea_id=?12)+ 3 处显式 SELECT: `:83`(list_active)/`:184`(advance_status_atomic)/`:205`(list_deleted)全加 idea_id;`impl_repo!` 宏(mod.rs:81/98/119)用 `SELECT *` 自动带列 | ✅ grep `SELECT.*FROM tasks` 仅 3 处全含 idea_id 无遗漏 | -| 4 | 白名单 | `crud/settings.rs:145-147`(`"idea_id"` 加入 tasks allowed_columns + 注释"非状态机收口字段,合法可写") | ✅ update_task 工具经白名单自动支持改 idea_id | -| 5 | AI 工具 + IPC | `tool_registry.rs:633-634`(create_task schema 加 `("idea_id","string",false)`)+ `:650-651`(handler `args.get("idea_id").and_then(as_str).filter(!is_empty).map(String)` 空串/null 视为不关联);`commands/task.rs:24-27`(CreateTaskInput `pub idea_id: Option`)+ `:85`(handler `input.idea_id.filter(!is_empty)`) | ✅ AI 工具 + IPC 双层一致空串语义 | -| 6 | 前端展示 | `types.ts:120-121`(TaskRecord `idea_id?:string`)+ `:133-135`(CreateTaskInput 同)+ `TaskDetail.vue:107-115`(router-link `:to="/ideas/${task.idea_id}"` 显 ideaTitle 非 id)+ `:222-227`(ideaTitle computed find idea by id 回退 id)+ `:402-409`(load Promise.all 加 ideaApi.list)+ `:443`(df-data-changed 监听 entity==='idea') | ✅ router-link 友好 + 回退 id + 数据变更联动 | -| 7 | 向后兼容 | `models.rs:80-81`(#[serde(default)] 老任务 None)+ 单向 1对1(grep 全 crates 无 `list_tasks_by_idea` 反向查询)+ 6 处 TaskRecord 构造点全补 idea_id(project_soft_delete.rs/ai_node.rs/task_advance_node.rs/df-mcp tools.rs create+update×2) | ✅ 无反向查询符合 1对1 起步约束 + 编译无遗漏 | - -- **复跑验证**(防 check 假绿): - - `cargo test -p df-storage --lib`: **47 passed 0 failed**(含 `migrations::tests::v20_legacy_db_adds_idea_id_column` + `v20_column_exists_skips_alter` 两测)✅ - - `vue-tsc --noEmit`: **EXIT 0** ✅ -- **复用印证**:tasks.idea_id (`migrations.rs:579`) 与 projects.idea_id (`:562`) 同 `TEXT REFERENCES ideas(id)` 模式,FK 引用语义一致。 - -- **⚪ WATCH-1**: `migrate_v20` schema_version 用普通 `INSERT`(非 `INSERT OR IGNORE`),重跑同版本会 PK 冲突报错——这是 v1-v19 全部迁移的统一模式(run() 流程 current_version<20 才调,正常无重跑),非本次引入。测试注释 `migrations.rs:1072-1074` 已诚实标注此语义。 -- **⚪ WATCH-2**: V20 测试用 mock legacy schema 直接调 migrate_v20(非完整 run() 链),新库真路径依赖 V9_SQL 列存在性(已 grep 核验 :579)。同 V21 测试模式(:844-862),非本次短板。 -- **待修项回流 todo**: **无** 🔴/🟡 项 - ---- - -### CR-260619-16 REFACTOR-260619-04 ToolCard.vue 拆分(1167→374 行·commit 74f3860) — ✅ 已审(PASS·⚪1) - -- **结论(2026-06-19·独立 grep/read 核验 commit 74f3860)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 -- **验证**: vue-tsc --noEmit EXIT 0 / git show 74f3860^:ToolCard.vue(拆分前 1167 行)与 ToolResultBody.vue 8 分支逐节点对比 / CSS class 集合 diff(原 1167 行 ToolCard 全部 .ai-tool-* 类 0 丢失)。 - -**6 维度逐项核验**: - -| # | 维度 | 判定 | -|---|------|------| -| 1 | 逻辑等价 | ✅ isFailed 在 ToolCard(卡片级 border/dot/sub)与 ToolResultBody(结果级 banner/result-failed)各持 computed,源逻辑一字相同(status!=='completed'→false / run_command exit_code / raw 正则 /^(执行失败\|Error:\|Failed:)/m);status watch 拆分等价——ToolResultBody 仅在 status==='completed' 挂载(:74 v-if),其 immediate watch(:206-211)初始化 cmdOutputExpanded/httpBodyExpanded=isToolFailure(tc) 语义与原合并 watch(completed 分支)等价;ToolCard watch(:194-199)仅留 approving 复位,immediate 初始非 pending_approval 时 approving=false/timer=null 无副作用 | -| 2 | 8 渲染分支完整 | ✅ ToolResultBody v-if/v-else-if 链顺序与原逐字对齐:read_file(6)/list_directory(21)/search_files(44)/write_file(61)/patch_file(71)/run_command(85)/http_request(103)/generic(125)。**关键**:run_command 条件无 `&& parsed`(原 :152 同无,因用 argString 取 command 不依赖 parsed)与其他 7 分支带 parsed 形成与原一致的条件错位;各分支内部文案/SVG/class 绑定逐节点等价 | -| 3 | 接口契约不变 | ✅ ToolCard props(tc/isExpanded/isContentExpanded)+ emits(toggle/expand-content/approve)逐字保留;唯一调用方 ToolCardList.vue:33-43 绑定零改动;MessageList/AiChat 经 ToolCardList 间接消费不直接引 ToolCard(grep 印证仅 ToolCardList:51 import);expand-content 冒泡链 ToolResultBody→ToolCard(:77 `(id)=>emit('expand-content',id)`)→ToolCardList→父级等价 | -| 4 | composable 抽离 | ✅ parseDiffLines(useToolCardRender:32-45)纯函数复现原内联逻辑(空行过滤 + +/-/ctx 分类 + maxLines 截断追加「…」ctx 占位);useToolCardHeader 6 函数(projectNameById/taskNameById/displayArgValue/toolDisplayName/argsEntries/httpHost)+ 2 常量表(PROJECT_ID_TOOL_ARG/TASK_ID_TOOL_ARG)与原 :441-557 逐字等价(getTc() 闭包替代 props.tc,taskNameById 形参 t→tk 纯命名差异);6 个 import 全用 | -| 5 | ToolCard 瘦身 | ✅ 374 行(<400 达标)。保留:容器+头部(:10-22)+Running 骨架(:28-32)+审批区(参数/diff 预览/按钮 :35-63)+rejected(:66-69)+ConfirmDialog(:82-87)+超时 toast(:90-92)+状态机(approving timer + onApprove + HIGH_RISK 白名单 + watch 复位 + onBeforeUnmount) | -| 6 | 行为等价 | ✅ diffLines(ToolCard:230)用 parseDiffLines(props.tc.diff) 无 maxLines(write_file 审批 diff,与原 :400-410 等价);resultDiffLines(ToolResultBody:202)用 parseDiffLines(parsed.value?.diff, 120)(patch_file 结果 diff,与原 :418-433 MAX_DIFF_LINES=120 等价)——两路径数据源/截断互不干扰;scoped style 迁移:原 .ai-tool-* 全部类集合 diff **0 丢失** | - -**主代核查·审查防污染印证**: -- 独立 git show 74f3860^ 取拆分前 ToolCard.vue 全文逐节点对比,不信 commit message 声明。 -- CSS class 集合 diff:原 ToolCard.vue 全部 .ai-tool-* 类经 sort -u 后与 [新 ToolCard ∪ 新 ToolResultBody] 集合做 comm -23 → **空集**(0 丢失)。 -- run_command 条件无 `&& parsed` 这处「条件错位」最易在拆分时被误改为统一加 parsed,独立核验 :85-86 与原 :151-152 逐字一致未动。 -- vue-tsc EXIT 0 独立复跑通过(主代已核,本次复验一致)。 - -- **⚪ WATCH-1**: 7 个 diff 相关 class(approval-diff/approval-reason/diff-line/diff-line--add/del/ctx/diff-pre)在 ToolCard 与 ToolResultBody **重复定义**(因 scoped 样式不跨组件,两组件各自渲染 diff 预览需各持一份)。定义内容逐字等价(仅格式差异),非冲突非回归,但后续若调 diff 视觉需同步两处(可考虑抽全局非 scoped 样式 DRY,低优先)。 -- **待修项回流 todo**: **无** 🔴/🟡 项 - ---- - -### CR-260619-17 REFACTOR-260619-06 ai_node.rs 拆分(836→423,抽 AiSelfReviewNode·commit c61a222) — ✅ 已审(PASS) - -- **结论(2026-06-19·独立 grep/read 核验 commit c61a222 + cargo test 复跑)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 -- **验证**: cargo test -p df-nodes 82 passed 0 failed 1 ignored(复跑通过,含 9 自审测试) / cargo check --workspace EXIT 0(零 warning) / 逐行 git show c61a222^ vs c61a222 对比 impl 体。 - -**6 维度逐项核验**: - -| # | 维度 | 判定 | -|---|------|------| -| 1 | 逻辑等价 | ✅ AiSelfReviewNode struct + `impl Node`(execute/schema/node_type)+ build_review_prompt 逐字一致(逐行 diff 核验,无字符级改动);Node trait 签名 `df_workflow::node::Node` 不变 | -| 2 | AiSelfReviewNode 抽出完整 | ✅ ai_self_review_node.rs(447)含 struct:39 + new:45 + build_review_prompt:50 + impl Node:73-262,符合一节点一文件约定(human_node/script_node/task_advance_node) | -| 3 | 测试共置 | ✅ 9 自审测试全迁入 ai_self_review_node::tests(update_field_writes_output_json / parse_review_json_valid_passes_through / parse_review_json_invalid_falls_back_unknown / parse_review_json_strips_markdown_code_fence / truncate_for_summary_long_text / build_review_prompt_contains_inputs / gate_disabled_never_blocks_any_verdict / gate_enabled_blocks_only_on_fail / gate_unknown_verdict_does_not_block);ai_node.rs 测试模块止于 glm_live_complete:387,无自审测试残留 | -| 4 | state.rs 路径引用 | ✅ grep 全 src-tauri+crates 确认 `ai_node::AiSelfReviewNode` **0 残留**;state.rs:714 已改 `df_nodes::ai_self_review_node::AiSelfReviewNode`(注释同步 :708) | -| 5 | 注册键/node_type 不变 | ✅ state.rs:713 `register("ai_self_review", ...)` 字符串键未动(git diff 仅 :708 注释 + :714 路径两行变更);node_type() 返 "ai_self_review" 不变(ai_self_review_node.rs:260);task_workflow_templates.rs 本 commit 未改(字符串引用 :66/67/80/137 基于字符串非 Rust 路径,不受影响) | -| 6 | params/self_review 未重复抽 | ✅ ai_node_helpers.rs 本 commit 零改动(git show 空 diff);8 helper 全 pub(crate):resolve_and_parse/provider_from_params/resolve_provider/parse_params/REVIEW_SYSTEM_PROMPT/parse_review_json/truncate_for_summary/gate_should_block;ai_self_review_node.rs:17-22 仅 import 未重定义;build_review_prompt 留 ai_self_review_node(AiSelfReviewNode 专属,非共享 helper) | - -**审查防污染印证**: -- 逐字搬运独立核验:对 ai_self_review_node.rs:75-262(execute impl 体)与 c61a222^:ai_node.rs:197-323 逐行比对,execute/schema/node_type 体字符级一致(含 review JSON 合并逻辑 / gate 闸门 return Err / schema params required 字段 / 注释文案),仅文件归属变更。 -- imports 精简合理:ai_self_review_node.rs 删原 glob `#[allow(unused_imports)]`(改显式列 5 个 helper),ai_node.rs 主代码 import 收敛到 2 个(execute 唯二用)+ tests 模块独立 import 3 个(parse_params/resolve_provider/ResolvedProvider),无 unused warning(cargo check 零 warning 印证)。 -- 行数账对:836(旧)≈ 423(ai_node.rs)+ 447(ai_self_review_node.rs)- 重复(imports/文档块重写),符合纯拆分预期。 -- task_workflow_templates.rs 字符串引用经核基于 "ai_self_review" 字面量(dag.add_node:66/edge:80/test:137),非 Rust 路径,模块拆分对其零影响——印证「注册键字符串不变」论断正确。 - -- **待修项回流 todo**: **无** 🔴/🟡 项 - ---- - -### CR-260619-18 对话列表脉冲点样式修复(vertical-align/margin-left 冲突·commit 5c395f4) — ✅ 已审(PASS) - -- **结论(2026-06-20·独立 grep/read 核验 commit 5c395f4 + vue-tsc EXIT 0)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 -**3 维度独立核验(commit 5c395f4 diff 1 文件 +1/-2,vue-tsc EXIT 0)**: - -| 维度 | file:line | 核验 | 判定 | -|------|-----------|------|------| -| 1 根因修复 | `AiChat.vue:813-821` | `.ai-conv-gen-dot` 当前无 `vertical-align`/`margin-left`;父 `.ai-conv-item-title:779-781` `display:inline-flex; align-items:center; gap:3px` 承载对齐与间距,无 4px 叠加(原 7px→现 3px 统一) | ✅ | -| 2 防挤出 | `AiChat.vue:782` + `:819` | `.ai-conv-item-title` 含 `min-width:0`(允许 inline-flex 收缩)+ 脉冲点 `flex-shrink:0`(不被 ellipsis 挤出),`text-overflow:ellipsis:777` 截标题不截脉冲点 | ✅ | -| 3 不破坏 | `ConversationSidebar.vue:50/132/177` | 3 处列表引用同 `.ai-conv-gen-dot` 作 `.ai-conv-item-title` 子元素,布局一致;其他属性保留(`display:inline-block/width:6px/height:6px/border-radius:50%/background/animation:820`),`@keyframes ai-conv-gen-pulse:822-825` 未动 | ✅ | - -- **验证**: `git show 5c395f4 --numstat` = `1 2 src/components/AiChat.vue`(仅 1 文件 +1/-2);Read `AiChat.vue:772-825` 当前形态逐字核验;vue-tsc --noEmit EXIT 0。 -- **⚪ WATCH-1**: `src/styles/global.css:234` 存在 `.ai-conv-gen-dot { outline:1px solid #0aa!important; background:#0aa!important; width:10px!important; height:10px!important }` TEMP DEBUG 参考线(强制 10px+青色),未提交非本 CR;调试用 `!important` 会覆盖生产 6px,上线前需移除(本次审查仅标注,不动手)。 -- **待修项回流 todo**: **无** 🔴/🟡 项 - ---- - -### CR-260619-19 AiChat 样式同步到子组件 scoped + 复用抽取(God 拆分遗漏修复·commit 4f8f1cf) — ✅ 已审(PASS·⚪1) - -- **结论(2026-06-20·独立 grep/read 核验 commit 4f8f1cf)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 — 纯样式移动无回归,God 拆分遗漏修复到位 -- **范围**(4 文件 +596/-742):AiChat.vue(scoped 886→151,瘦身 83%)+ ConversationSidebar.vue(新建