diff --git a/docs/02-架构设计/aichat交互体验改进方案-2025-07-15.md b/docs/02-架构设计/aichat交互体验改进方案-2025-07-15.md new file mode 100644 index 0000000..409e20e --- /dev/null +++ b/docs/02-架构设计/aichat交互体验改进方案-2025-07-15.md @@ -0,0 +1,295 @@ +# AIChat 交互体验改进方案 + +> 创建: 2025-07-15 | 状态: 待讨论 +> 范围: 消息发送、流式渲染、对话管理、错误恢复、技能/Provider、窗口布局等非授权类交互 + +--- + +## 一、消息输入与发送 + +### 1.1 无法编辑已发送消息 + +**现象**:用户发出消息后发现措辞有误,只能重新打一条新消息。无法像 ChatGPT/Claude 那样编辑上一条用户消息并重新生成。 + +**根因**:`sendMessage`(useAiSend.ts)push 后的消息是只追加不可变的。前端没有编辑入口,后端 `ai_chat_send` 也没有"替换最后一条 user 消息并重跑"的语义。 + +**方案**: +- 用户消息气泡 hover 显示"编辑"按钮 +- 点击后消息内容回填到输入框,用户修改后发送时后端截断该消息之后的所有历史(含 AI 回复),重新跑 agentic loop +- 后端新增 `ai_chat_edit` 命令,接收 `message + 截断位置`,替换 messages 数组中对应 user 消息并清掉后续消息,然后走正常 `run_agentic_loop` + +### 1.2 无法重新生成 AI 回复 + +**现象**:AI 回答不满意时没有"重新生成"按钮,只能重新措辞追问。 + +**根因**:`AiChat.vue` 的 AI 消息气泡上没有任何操作按钮。后端也没有 `ai_regenerate` 命令。 + +**方案**: +- AI 消息气泡 hover 显示操作栏(复制 | 重新生成) +- "重新生成":后端删除最后一条 AI 消息,用倒数第二条 user 消息重新触发 `run_agentic_loop` +- 后端新增 `ai_regenerate` 命令 + +### 1.3 无法一键复制消息内容 + +**现象**:代码或文本只能手动选中复制。流式渲染中选中文字会被后续 delta 打断。 + +**根因**:消息气泡上没有复制按钮。 + +**方案**: +- AI 消息气泡 hover 显示"复制"按钮 +- 点击后 `navigator.clipboard.writeText(msg.content)`,toast 提示"已复制" +- 代码块单独提供"复制代码"按钮(hover 代码块右上角浮出) + +### 1.4 缺少 `@` 实体引用 + +**现象**:用户无法在输入时引用某个项目、任务或文件。描述需求时无法精确指定上下文,AI 可能猜错对象。 + +**根因**:输入框只有 `/` 技能联想,没有 `@` 实体引用机制。 + +**方案**: +- 输入框支持 `@` 触发实体联想浮层(复用技能联想的 popover 架构) +- 联想源:项目列表、任务列表、最近编辑的文件 +- 选中后在消息中展开为 `[项目: u-desk]` 等标记文本,后端 system prompt 注入对应实体的上下文摘要 + +### 1.5 输入框高度过紧 + +**现象**:textarea 最大高度 120px(约 5-6 行),超过后内部滚动。用户写长提示词时看不到全貌。 + +**根因**:`autoResize` 中 `Math.min(el.scrollHeight, 120)` 限制太紧。 + +**方案**: +- 最大高度提升到 200px(约 10 行),超过后再内部滚动 +- 或改为可拖拽调整高度(底部 resize handle) + +--- + +## 二、流式渲染与消息展示 + +### 2.1 流式渲染中无法稳定选中文字 + +**现象**:AI 正在流式输出时,用户尝试选中已渲染的文字,新的 delta 触发 DOM 更新导致选区丢失。 + +**根因**:`renderContent` 在流式时返回 `streamingHtml.value`(rAF 节流重 parse),每次更新都 `v-html` 替换整个 DOM 子树,浏览器选区被清除。 + +**方案**: +- 方案 A(推荐):检测到用户正在选择文字时(`selectionchange` 事件 + 选区非空且在消息容器内),暂停 rAF 流式 parse,选区结束后恢复 +- 方案 B:流式渲染时在已完成的块(blockCache 命中的块)上使用独立 DOM 节点不参与 v-html 替换,仅末块动态更新 + +### 2.2 代码块无语法高亮、无复制按钮 + +**现象**:Markdown 代码块只有纯文本渲染,没有语法高亮,也没有一键复制按钮。 + +**根因**:`useMarkdown` composable 配置了 marked + DOMPurify,但没有集成 highlight.js / Prism 等高亮器。 + +**方案**: +- 集成 highlight.js(体积小、语言全),在 marked renderer 的 `code` 回调中调用 `hljs.highlightAuto` 或按 info string 指定语言 +- 代码块右上角浮出"复制"按钮(纯前端,hover 显示) +- 高亮器按需加载(与 marked 一样后台预热,不阻塞首屏) + +### 2.3 历史消息无分页懒加载 + +**现象**:切换对话时 `switchConversation` 一次性加载全部 messages 到 `state.messages`。超长对话可能一次加载几百条消息。 + +**根因**:`switchConversation`(useAiConversations.ts)直接 `state.messages = rawMsgs.filter().map()`,无分页。 + +**方案**: +- 首次加载最近 50 条,滚动到顶部时加载更多(前端 slice + 后端支持 offset/limit 查询 messages) +- 或前端全量加载但虚拟滚动只渲染可视区域(见 7.2) + +### 2.4 消息不显示时间戳 + +**现象**:消息气泡上不显示发送时间。用户无法判断某条消息是多久前发的。 + +**根因**:模板中 AI/用户消息都没有渲染 `msg.timestamp`。 + +**方案**: +- 消息气泡下方或侧边以极小字号(9px)+ dim 颜色展示相对时间(`formatRelativeZh`) +- hover 时 tooltip 展示完整时间 + +--- + +## 三、对话管理 + +### 3.1 对话搜索缺失 + +**现象**:侧栏对话列表只能滚动浏览,没有搜索框。对话多了之后找不到特定对话。 + +**根因**:侧栏 `.ai-conv-list` 直接渲染 `groupedActive`,上方只有"新建"按钮,没有搜索输入框。 + +**方案**: +- 侧栏 header 下方增加搜索输入框(实时过滤 `state.conversations`,匹配 title) +- 搜索时取消分组,按相关度/时间平铺展示 +- 支持 `Ctrl+K` 快捷键聚焦搜索框 + +### 3.2 对话无法置顶 + +**现象**:没有置顶或收藏功能。重要对话会被新对话挤到下面。 + +**根因**:对话列表只按 `updated_at` 排序,无 `pinned` 字段。 + +**方案**: +- `ai_conversations` 表增加 `pinned INTEGER DEFAULT 0` +- 排序逻辑改为 `pinned DESC, updated_at DESC` +- 侧栏对话项 hover 显示"置顶/取消置顶"按钮(图钉图标) + +### 3.3 对话无法导出 + +**现象**:无法将对话导出为 Markdown / JSON / 文本文件。 + +**根因**:没有导出 IPC 命令和前端入口。 + +**方案**: +- 后端新增 `ai_conversation_export(conv_id, format)` 命令,支持 `markdown` / `json` / `txt` 三种格式 +- 前端侧栏对话项 hover 显示"导出"按钮,或对话操作菜单中提供 +- Markdown 格式:`## 用户` / `## 助手` 交替,代码块保留围栏 + +### 3.4 新建对话强制中断已有生成 + +**现象**:用户在 AI 生成中点"新建对话",后端 `ai_conversation_create` 会强制复位 `generating=false` + 置 `stop_flag`,中断当前生成。 + +**根因**:`commands.rs ai_conversation_create` 中 `if session.generating` 分支主动结束生成(B-260615-10 设计)。 + +**方案**: +- 生成中点"新建对话"时弹 ConfirmDialog:"当前对话正在生成,确定要新建对话并中断吗?" +- 用户确认后才执行中断+新建;取消则不操作 +- 或改为不中断:新对话仅切换视图,旧对话后台继续生成(需 AiSession 多实例化支持,属远期方案) + +--- + +## 四、错误处理与恢复 + +### 4.1 错误气泡无操作入口 + +**现象**:错误气泡只显示一行文字(如 `[GLM-4] AI 调用失败(HTTP 401): Unauthorized`),没有"重试"按钮或"去设置"链接。 + +**根因**:`AiError` 事件只携带 `error: String`,前端 `handleEvent` push 一条 `isError: true` 的消息气泡,无操作按钮。 + +**方案**: +- 错误气泡底部增加操作按钮区: + - **重试**:取上一条 user 消息重新发送(调 `ai_chat_send`) + - **去设置**(仅 401/403/Provider 未配置时显示):跳转到 Settings → AI Tab +- 错误消息结构扩展:后端 `AiError` 增加 `error_type: Option` 枚举(`auth` / `network` / `timeout` / `provider_config` / `unknown`),前端据此决定显示哪些按钮 + +### 4.2 流式中断丢失已生成文本 + +**现象**:网络波动导致流式中断(idle timeout 或 mid-stream error),已接收的文本被丢弃(`stream_llm` return None → `agentic.rs` emit AiError 并退出),用户看到错误气泡,之前生成的几百字全部丢失。 + +**根因**:`stream_llm` 遇到错误时 `return None`,不保留已接收的部分文本。`agentic.rs` 收到 None 后直接结束。 + +**方案**: +- `stream_llm` mid-stream error 时改为返回 `Some((partial_text, tool_calls, usage))` + 一个 `incomplete: bool` 标志 +- `agentic.rs` 收到 incomplete=true 时: + - 已有文本正常入库(标注 `truncated`) + - emit `AiCompleted`(而非 AiError)让前端正常展示已生成内容 + - 在消息末尾追加系统提示:"⚠ 响应因网络中断不完整" +- 前端 AI 消息气泡底部显示"继续生成"按钮(用最后一条 user 消息重新触发,后端识别不完整消息做续写或重跑) + +--- + +## 五、技能与 Provider + +### 5.1 Provider 切换零反馈 + +**现象**:点击 provider bar 循环切换 provider(`cycleProvider`),切换后只在 bar 上显示名字变化,没有 toast 或动画反馈。 + +**根因**:`cycleProvider` 直接调 `store.setProvider`,无 UI 反馈。 + +**方案**: +- 切换后 toast 提示"已切换到 {providerName}" +- provider bar 切换时加 0.15s 淡入动画 +- bar 上增加 provider 状态指示(model 名称小字展示) + +### 5.2 技能联想不展示参数用法 + +**现象**:技能联想浮层显示 name + description + source,但不显示技能的参数格式或示例。选中后 placeholder 里的 `argument_hint` 太短。 + +**根因**:浮层设计只展示概要信息,`argument_hint` 仅在选中后作为 placeholder 显示。 + +**方案**: +- 联想浮层每项增加一行参数提示(`argument_hint` 以等宽字体小字展示) +- 选中技能后输入框上方 chip 展示完整的参数格式说明(而非仅 name + description) +- 或选中技能后自动在输入框填入模板骨架(如 `/commit `),光标定位到参数位置 + +--- + +## 六、窗口与布局 + +### 6.1 侧栏宽度不可调 + +**现象**:侧栏固定 160px,不可拖拽调整宽度。长对话标题被截断。 + +**根因**:`.ai-conv-sidebar { width: 160px; min-width: 160px; }` 硬编码。 + +**方案**: +- 侧栏右边缘增加 2px 拖拽条(`cursor: col-resize`) +- 拖拽时实时更新 width,范围 120~280px +- 宽度持久化到 `df-ai-ui` 设置(与 sidebarOpen/maximized 同存) + +### 6.2 分离窗口关闭后生成态不同步 + +**现象**:分离窗口关闭时,如果正在生成,主窗口虽然 `panelOpen=true` 恢复,但 `state.streaming` / `state.currentText` 可能不同步——如果生成中的对话不是主窗口当前活跃对话,主窗口看不到生成态。 + +**根因**:分离窗口关闭走 `closeDetachedWindow`,清了 localStorage 快照,但主窗口的状态依赖事件路由自然恢复。 + +**方案**: +- 分离窗口关闭时,主窗口检测 `state.generatingConvId` 非空,自动切换到正在生成的对话 +- 或在主窗口 header 显示"对话 X 正在生成中"提示条,点击切换过去 + +--- + +## 七、其他交互细节 + +### 7.1 无键盘快捷键 + +**现象**:除了 Enter 发送 / Shift+Enter 换行,没有其他快捷键。 + +**方案**: + +| 快捷键 | 功能 | +|--------|------| +| `Ctrl+N` | 新建对话 | +| `Ctrl+K` | 搜索对话 | +| `Ctrl+L` | 清空当前对话 | +| `Ctrl+Shift+C` | 复制最后一条 AI 消息 | +| `Ctrl+R` | 重新生成最后一条 AI 回复 | +| `Esc` | 关闭面板(嵌入模式)/ 关闭窗口(分离模式) | +| `Ctrl+B` | 切换侧栏 | + +### 7.2 消息列表无虚拟滚动 + +**现象**:消息列表直接 `v-for` 渲染所有消息,长对话几百条消息全量渲染 DOM,滚动卡顿。 + +**根因**:没有使用虚拟滚动库。 + +**方案**: +- 集成 `vue-virtual-scroller` 或自研 IntersectionObserver 懒渲染 +- 仅渲染可视区域 ±缓冲区的消息节点 +- 注意:流式渲染的最后一条消息需要始终保持挂载 + +### 7.3 空状态无引导 + +**现象**:首次打开 AI Chat(有 provider 配置),空状态只显示标题+提示语,没有示例问题或快捷操作。 + +**方案**: +- 空状态展示 3-4 个示例问题卡片(如"帮我创建一个新项目"、"查看当前任务列表"、"分析这段代码的问题") +- 点击卡片自动填入输入框并发送 +- 无 provider 配置时展示"去配置 AI Provider"引导按钮 + +### 7.4 对话标题生成对用户不透明 + +**现象**:对话标题由后端 `ensure_conversation_title` 异步生成,侧栏对话从"新对话"突然变成某个标题,没有过渡。 + +**方案**: +- 标题生成后加 0.3s 淡入动画 +- 或在标题前加小图标标识"AI 自动生成"(可 hover 查看) + +--- + +## 八、落地优先级 + +| 批次 | 痛点 | 理由 | +|------|------|------| +| **第一批** | 2.1 流式选中文字、1.2+1.3 消息操作栏(复制/重新生成)、4.1 错误重试、4.2 断线保文 | 每次对话都会遇到,最高频痛点 | +| **第二批** | 2.2 代码高亮+复制、3.1 对话搜索、7.1 键盘快捷键、3.4 新建不中断生成 | 显著提升日常效率 | +| **第三批** | 1.1 编辑重发、1.4 `@` 引用、3.3 导出、7.2 虚拟滚动、6.1 侧栏可调 | 按需推进,锦上添花 | +| **第四批** | 2.3 分页加载、2.4 时间戳、3.2 置顶、5.1+5.2 技能/Provider 反馈、7.3+7.4 空状态/标题 | 打磨细节 | diff --git a/docs/02-架构设计/aichat授权体验改进方案-2025-07-15.md b/docs/02-架构设计/aichat授权体验改进方案-2025-07-15.md new file mode 100644 index 0000000..166cef1 --- /dev/null +++ b/docs/02-架构设计/aichat授权体验改进方案-2025-07-15.md @@ -0,0 +1,282 @@ +# AIChat 授权功能体验改进方案 + +> 创建: 2025-07-15 | 状态: 待讨论 + +## 一、当前授权机制概览 + +### 1.1 核心流程 + +``` +用户发送消息 + → Agentic Loop (最多 10 轮) + → LLM 流式响应 + → 解析 tool_calls + → 按风险等级分流: + ├─ Low Risk → 自动并行执行(join_all) + ├─ Medium → 暂停循环,等待人工审批 + └─ High → 暂停循环,等待人工审批 + → 审批通过 → 执行工具 → try_continue_agent_loop 恢复循环 + → 审批拒绝 → tool_result="用户拒绝了此操作" → 恢复循环让 LLM 自行决策 +``` + +### 1.2 工具风险分级矩阵 + +| 风险等级 | 工具 | 执行方式 | +|---------|------|---------| +| **Low** | `list_projects`, `list_tasks`, `list_ideas`, `list_trash`, `read_file`, `list_directory` | 自动执行,并行 `join_all` | +| **Medium** | `create_project`, `create_task`, `create_idea`, `update_project`, `update_task`, `bind_directory`, `write_file` | 需人工审批 | +| **High** | `delete_project`, `restore_project`, `purge_project`, `delete_task`, `run_workflow`, `run_command` | 需人工审批 | + +### 1.3 已实现的亮点 + +- **审计留痕**:`ai_tool_executions` 表完整记录每次工具调用的状态、风险等级、决策者、请求时间和执行时间 +- **断点恢复**:应用重启后通过 `restore_pending_approvals()` 从数据库重建内存中的待审批状态 +- **审批卡片可读化**:`build_approval_reason()` 解析工具参数,拼接人类可读的审批理由 +- **generating 状态 RAII 守护**:`GeneratingGuard` 确保无论正常退出还是 panic,`generating` 标志都能复位 +- **数据变更联动刷新**:工具执行成功后自动 emit `df-data-changed` 事件,前端自动刷新列表 +- **安全防护**:API Key 存 OS keyring、文件路径双层校验、write_file 覆盖前自动 .bak 备份 + 原子写 + +### 1.4 关键代码位置 + +| 模块 | 文件 | 职责 | +|------|------|------| +| 工具注册表 | `src-tauri/src/commands/ai/tool_registry.rs` | 工具定义 + 风险等级 + handler 同源 | +| 审批处理 | `src-tauri/src/commands/ai/audit.rs` | 工具调用审计 + pending 审批恢复 + 审批后状态回填 | +| Agentic 循环 | `src-tauri/src/commands/ai/agentic.rs` | 流式接收 → 工具执行 → 结果回传 LLM → 循环 | +| IPC 命令 | `src-tauri/src/commands/ai/commands.rs` | `ai_approve` / `ai_chat_send` / `ai_chat_stop` 等 | +| 审批卡片 UI | `src/components/ToolCard.vue` | 卡片渲染 + 参数展示 + 批准/拒绝按钮 | +| 卡片列表 | `src/components/ToolCardList.vue` | 多卡片折叠管理 | +| 聊天面板 | `src/components/AiChat.vue` | 消息列表 + 审批转发 + 输入区 | +| AI Store | `src/stores/ai.ts` | 模块级单例 state | +| 发送/审批 | `src/composables/ai/useAiSend.ts` | `approveToolCall` 乐观更新 + IPC 调用 | + +--- + +## 二、用户体验痛点分析 + +### 痛点 1:逐个审批,无法批量操作 + +**现象**:当 AI 一次返回多个 Medium/High 工具调用时(比如同时创建 3 个任务 + 写 2 个文件),用户需要逐个点批准/拒绝。 + +**根因**:`process_tool_calls`(audit.rs)把每个待审批工具都推入 `pending_approvals` HashMap,前端为每个 pending 渲染独立的 ToolCard,每个卡片只有自己的"批准/拒绝"按钮。`ToolCardList.vue` 没有批量操作入口。 + +**影响**:高频交互场景下审批变成体力活,用户体验疲劳。 + +### 痛点 2:审批阻塞整个对话,期间无法输入 + +**现象**:审批等待期间 `generating=true`,用户虽然能上滑看历史,但无法发送新消息(`sendMessage` 会入队),也无法预知还有多少审批在排队。 + +**根因**:`ai_chat_send`(commands.rs:46)检查 `session.generating` 为 true 时拒绝新消息。审批等待期间 `generating` 保持 true(`agentic.rs` 的 `guard.disarm()` 保持 true 以便 `try_continue` 续生成)。 + +**影响**:用户处于"被动等待"状态,无法并行做其他事。 + +### 痛点 3:Medium 和 High 体验无差异 + +**现象**:两者都弹同样的审批卡片、同样的按钮,交互流程完全一致。 + +**根因**:`process_tool_calls`(audit.rs:103-120)中 Medium 和 High 走同一个 `pending_approvals.insert` 分支,区别仅在 `build_approval_reason` 的文案后缀("请确认是否执行" vs "高风险,需人工批准")。 + +**影响**:High 操作(如 `purge_project` 不可恢复)缺乏足够的警示力度,容易误操作。 + +### 痛点 4:write_file 审批信息不够直观 + +**现象**:`write_file` 是 Medium 风险,审批卡片只展示 path 和 content 参数。content 可能是几百行代码,在审批卡片里以 `formatArgValue` 截断到 300 字符展示。 + +**根因**:`ToolCard.vue` 的 `toolArgsEntries` → `displayArgValue` → `formatArgValue` 对超长值统一截断到 300 字符。 + +**影响**:用户无法看清要写入的完整内容,只能盲目批准。尤其覆盖已有文件时,用户不知道会改什么。 + +### 痛点 5:审批后无进度反馈 + +**现象**:点击"批准"后,卡片乐观置 `running`,但用户不知道: +- 工具正在执行还是已执行完等待 LLM 续生成 +- 还有多少待审批在排队 +- 整个 agentic 循环进行到第几轮 + +**根因**:前端没有全局的 agentic 循环进度指示器。`AiAgentRound` 事件虽然通知了轮次,但没有在 UI 上持久化展示。 + +**影响**:用户对系统状态缺乏掌控感,尤其在多轮工具调用时。 + +### 痛点 6:审批卡片可能被滚出视口 + +**现象**:当消息很多时,审批卡片可能被新消息推到上方滚出视口。 + +**根因**:`AiChat.vue` 的 `collapseInactive` 不会收起 `pending_approval` 卡片,但也不会自动滚动到 pending 卡片。没有全局徽标提醒。 + +**影响**:用户可能看不到待审批项,对话看起来"卡住了"但不知道在等什么。 + +--- + +## 三、改进方案 + +### P0 — 快速改善体感 + +#### 3.1 批量审批 + +**方案**:当同一轮有多个 pending 时,在 ToolCardList 顶部显示"全部批准(N) / 全部拒绝"按钮。 + +**改动范围**: +- `ToolCardList.vue`:新增批量操作栏,监听 toolCalls 中 pending_approval 数量 +- `useAiSend.ts`:新增 `approveAll(rejectAll)` 方法,循环调用 `ai_approve` + +**交互**: +``` +┌─────────────────────────────────┐ +│ ⏳ 3 项待审批 │ +│ [✓ 全部批准] [✕ 全部拒绝] │ +├─────────────────────────────────┤ +│ [工具卡片 1 - pending] │ +│ [工具卡片 2 - pending] │ +│ [工具卡片 3 - pending] │ +└─────────────────────────────────┘ +``` + +#### 3.2 审批计数器 + 跳转 + +**方案**:在输入框上方或 header 显示 `⏳ 2 项待审批`,点击跳转到第一个 pending 卡片。 + +**改动范围**: +- `AiChat.vue`:header 区域增加审批徽标 +- `ToolCardList.vue`:暴露 `scrollToFirstPending` 方法 + +**交互**: +``` +┌──────────────────────────────────┐ +│ 🤖 助手 ⏳2 [+][×] │ ← 徽标在 header +├──────────────────────────────────┤ +│ ...消息列表... │ +│ ┌─ 工具卡片 (pending) ──┐ │ ← 点击徽标滚动到此 +│ │ 创建任务:XXX │ │ +│ │ [批准] [拒绝] │ │ +│ └────────────────────────┘ │ +├──────────────────────────────────┤ +│ [输入框] │ +└──────────────────────────────────┘ +``` + +#### 3.3 write_file diff 预览 + +**方案**:write_file 审批时,如果文件已存在,展示前后对比 diff 而非裸 content。 + +**改动范围**: +- 后端 `tool_registry.rs`:write_file handler 在执行前读取旧文件内容,返回 diff 信息(或前端请求 diff) +- `ToolCard.vue`:pending_approval + name=write_file 时渲染 diff 视图 + +**交互**: +``` +┌─ 写入文件 src/main.rs (待审批) ──────┐ +│ │ +│ - fn main() { │ ← 红色:删除行 +│ - println!("hello"); │ +│ + fn main() { │ ← 绿色:新增行 +│ + println!("hello, world"); │ +│ + setup_logging(); │ +│ │ +│ ⚠ 覆盖已有文件 (23→45 行) │ +│ [查看完整内容] [批准] [拒绝] │ +└───────────────────────────────────────┘ +``` + +--- + +### P1 — 增强控制力 + +#### 3.4 自动审批策略(信任模式) + +**方案**:Settings 中增加"自动审批"配置,用户可选择对特定风险等级或工具类型自动放行。 + +**配置项**: +``` +Settings → AI → 自动审批策略 + ○ 严格模式(默认):所有 Medium/High 需人工审批 + ○ 宽松模式:Medium 自动放行,High 需人工审批 + ○ 自定义:按工具类型选择 + ☑ write_file(workspace 内自动放行) + ☑ create_task / create_idea(自动放行) + ☐ delete_*(始终需审批) + ☐ run_command(始终需审批) +``` + +**改动范围**: +- `df-storage`:`app_settings` 表存储配置(KV 已有 V13 表) +- `audit.rs`:`process_tool_calls` 读取配置,决定 Medium 工具是否进 pending 或直接执行 +- `Settings.vue`:新增配置面板 + +#### 3.5 High 二次确认 + +**方案**:delete/purge/run_command 等高风险操作,批准后弹出二次确认。 + +**改动范围**: +- `ToolCard.vue`:High 风险 + approved=true 时,先弹 inline 确认("确定要永久删除?此操作不可恢复") +- 或用现有 `ConfirmDialog` 组件 + +**交互**: +``` +第一次点击"批准": + → 卡片内弹出确认提示 + → "确定要永久删除项目「XXX」?此操作不可恢复" + → [确认删除] [取消] + +第二次点击"确认删除": + → 才真正执行 ai_approve +``` + +#### 3.6 审批超时 + +**方案**:可配置超时自动拒绝(默认 5 分钟),避免对话永久卡住。 + +**改动范围**: +- 后端:`AiSession` 增加 pending 审批的 `created_at` 时间戳,定时检查超时 +- 或前端:`useAiSend.ts` 在 pending 时启动定时器,超时自动调 `ai_approve(id, false)` + +--- + +### P2 — 信息透明度 + +#### 3.7 Agentic 进度条 + +**方案**:在消息区域底部显示循环进度。 + +**改动范围**: +- `AiChat.vue`:底部增加进度指示条 +- `useAiEvents.ts`:处理 `AiAgentRound` 事件时更新进度 + +**交互**: +``` +┌──────────────────────────────────┐ +│ ...消息列表... │ +│ │ +│ 🔄 循环 3/10 · ⏳2待审批 · ✅5完成 │ ← 底部进度条 +├──────────────────────────────────┤ +│ [输入框] │ +└──────────────────────────────────┘ +``` + +#### 3.8 审批历史面板 + +**方案**:独立页面展示 `ai_tool_executions` 表的审计记录。 + +**改动范围**: +- 后端:新增 `list_tool_executions` IPC 命令(查询审计表) +- 前端:新增 `AuditLog.vue` 视图,表格展示历史记录 + +**展示字段**: +| 时间 | 工具 | 风险 | 状态 | 决策者 | 参数摘要 | 结果摘要 | +|------|------|------|------|--------|---------|---------| + +--- + +## 四、实施优先级建议 + +| 优先级 | 改进项 | 预估工作量 | 用户价值 | +|--------|--------|-----------|---------| +| **P0** | 3.1 批量审批 | 0.5 天 | 🔥🔥🔥 | +| **P0** | 3.2 审批计数器 + 跳转 | 0.5 天 | 🔥🔥🔥 | +| **P0** | 3.3 write_file diff 预览 | 1 天 | 🔥🔥 | +| **P1** | 3.4 自动审批策略 | 1.5 天 | 🔥🔥🔥 | +| **P1** | 3.5 High 二次确认 | 0.5 天 | 🔥 | +| **P1** | 3.6 审批超时 | 0.5 天 | 🔥 | +| **P2** | 3.7 Agentic 进度条 | 0.5 天 | 🔥🔥 | +| **P2** | 3.8 审批历史面板 | 1 天 | 🔥 | + +**建议第一批落地**:P0 三项(批量审批 + 计数器 + diff 预览),总计约 2 天工作量,覆盖最高频的体验痛点。 diff --git a/docs/02-架构设计/patch_file工具设计-2026-06-15.md b/docs/02-架构设计/patch_file工具设计-2026-06-15.md new file mode 100644 index 0000000..52f74e4 --- /dev/null +++ b/docs/02-架构设计/patch_file工具设计-2026-06-15.md @@ -0,0 +1,360 @@ +# Patch File 工具设计 + +> 创建: 2026-06-15 | 状态: 设计定稿待实施 | 优先级: P0 +> 关联 todo: F-260615-06 [P0] + +--- + +## 一、问题定义 + +### 1.1 现状缺口 + +DevFlow AI agent 有 `write_file`(全量覆盖写入)但**无局部编辑能力**。AI 需要修改文件中某几行时只能: + +``` +read_file → AI 在 context 中拼出完整新内容 → write_file 全量覆盖 +``` + +**问题链**: +1. **Token 浪费**:大文件(几百行)只改 3 行却要重发全部内容 +2. **事故风险**:write_file 全量覆盖已出事故(PROGRESS.md 762 行 → 248 字节,FR-S7 记录) +3. **无审计粒度**:无法知道「改了哪里」,只有「整个文件被替换了」 +4. **并发不安全**:join_all 并行场景下多工具操作同一文件无保护 + +### 1.2 目标 + +提供 `patch_file` 工具,让 AI 能做**精确的局部文本替换**,形成完整的文件操作闭环: + +``` +read_file(读) → search_in_file(定位) → patch_file(改) → run_command(验证) +``` + +--- + +## 二、API 设计 + +### 2.1 请求结构 + +```rust +/// 局部文件更新请求 +struct PatchFileRequest { + /// 目标文件路径(必填,走 validate_path 校验 + 黑名单) + path: String, + + /// 文件指纹(可选):用于检测外部修改 + /// 格式: "{unix_timestamp}_{size}" 如 "1718400000_12345" + /// 由 read_file 返回的 file_hash 字段携带 + expected_hash: Option, + + /// 有序补丁列表(从文件末尾往前执行,避免行号偏移) + patches: Vec, +} + +/// 单个补丁 +struct Patch { + /// 必填:要替换的旧文本(精确匹配 = 乐观锁) + old_text: String, + + /// 必填:替换后的新文本 + new_text: String, + + /// 可选:行号辅助定位(快速跳转 + 去歧增强) + /// 有值时优先跳到该行检查 old_text;不匹配则降级全文扫描 + line: Option, + + /// 可选:old_text 之前的上下文锚(去歧——多匹配时精确锁定) + before_text: Option, + + /// 可选:old_text 之后的上下文锚(去歧) + after_text: Option, +} +``` + +### 2.2 响应结构 + +```rust +struct PatchResult { + success: bool, + patches_applied: usize, // 成功替换的 patch 数 + total_matches: usize, // 每个 patch 的总命中数(含未替换的) + lines_changed: i32, // 总行数变化(正=增加 负=减少) + warnings: Vec, // ["匹配到 3 处,仅替换第 1 处"] + file_hash: String, // 操作后的新指纹(下次操作用) +} +``` + +### 2.3 read_file 扩展(返回指纹) + +现有 read_file 返回值新增字段: + +```rust +struct ReadFileResult { + path: String, + content: String, + size: u64, + lines: u32, + // 新增: + modified: String, // ISO8601 或 unix timestamp + file_hash: String, // mtime+size 指纹,如 "1718400000_12345" +} +``` + +### 2.4 LLM 描述(tool_registry 注册用) + +``` +"局部更新文件内容。用于精确修改文件的特定部分(而非全量覆盖)。 + 每个补丁指定 old_text(要替换的原文)和 new_text(新内容)。 + 可选 line 辅助定位、before/after_text 上下文锚定消除歧义。 + 属 Medium 风险操作(修改已有文件),需人工审批。 + 注意:old_text 必须与文件内容完全匹配(含空格/缩进);若文件已被外部修改, + 请先重新 read_file 获取最新内容和 file_hash。" +``` + +--- + +## 三、核心决策记录 + +### 决策 1:定位方式 — old_text 精确匹配为主,line 为辅 + +| 方案 | 示例 | 优点 | 缺点 | +|------|------|------|------| +| **A: old_text ✅ 选定** | 匹配 "fn main() {" 替换 | =隐式乐观锁;AI零认知负担(复制即用);内容变了自动冲突报错 | 改动大时 old_string 长 | +| B: line 行号 | 替换第 42 行 | 短小精悍 | 文件改了行号偏移;AI需先search定位(多一轮IPC) | +| C: 正则 regex | 匹配 `/return Err\(.*\)/` | 表达力强 | AI生成regex易出错;转义复杂 | + +**折中**:old_text **必填**(主定位),line **可选**(辅助快速跳转+去歧),before/after_text **可选**(多匹配去歧)。 + +**理由**: +- AI 做 edit 时天然持有「要改哪段」上下文,复制即用(最小认知路径) +- old_text 天然防并发冲突(CAS 语义:Compare-And-Swap) +- line 不单独使用(无内容校验=盲替换,并发不安全) + +### 决策 2:多匹配处理 — 替换第 1 处 + warning + +``` +文件中有 N(N>1) 处相同 old_text: + → 仅替换第 1 处 + → 返回 warning: "⚠️ 匹配到 N 处,仅替换第 1 处" + → AI 收到 warning 后可加 before/after_text 缩小范围重试 +``` + +**不选**「全部替换」(太危险,可能批量错改)或「拒绝执行」(太严格,第 1 处往往就是目标)。 + +### 决策 3:并发安全 — 文件级 Mutex(不用队列) + +#### 为什么不用队列 + +| 维度 | 通用文件锁队列 | DevFlow 实际需要 | +|------|-------------|-----------------| +| 范围 | 全局、所有会话、所有文件 | 仅 join_all 并发窗口 + 远期多会话 | +| 粒度 | 每文件独立 FIFO 队列 | 文件级 Mutex 就够 | +| 复杂度 | 高(调度/超时/死锁检测) | **极低(~15行)** | +| 场景 | 多用户 / 分布式 | 单进程单用户桌面应用 | + +#### 当前真实并发源 + +``` +唯一并行点: audit.rs:338 join_all — Low 风险工具并行执行 +典型场景: AI 同时 list_projects + read_file + (未来) patch_file 同一文件 +``` + +#### 实现 + +```rust +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Mutex; +use once_cell::sync::Lazy; + +/// 全局文件锁表:每个路径一把互斥锁 +static FILE_LOCKS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + +// handler 内使用: +let abs_path = validated_path.canonicalize()?; +let _guard = FILE_LOCKS + .lock() + .entry(abs_path) + .or_insert_with(|| ()); +// guard drop 时自动释放 +``` + +**效果**:同一文件读写串行化,不同文件仍并行。Mutex 释放后后续操作继续。 + +### 决策 4:外部脏写防御 — expected_hash 指纹校验 + +#### 指纹选型 + +| 方案 | 精度 | 开销 | 适用 | +|------|------|------|------| +| **mtime + size ✅ 选定** | 秒级 | ~μs(一次 metadata 调用) | 本地桌面应用 | +| blake3/sha256 | 内容级 | 大文件 ms 级 | 需要密码学强度时 | +| inode + mtime | Unix 语义 | ~μs | Windows inode 不同 | + +选 **mtime + size**:DevFlow 是本地桌面应用,「外部修改」= 用户切 VS Code 改了几行再回来,时间差 >1s。实现最简单。 + +#### 流程 + +``` +read_file(path) → { content, file_hash: "1718400000_23456" } + ↓ +AI 基于内容决策 ↓ +patch_file({ path, expected_hash: "1718400000_23456", ... }) + ↓ +后端: + ① current_meta = metadata(path) + ② current_hash = format!("{}_{}", modified.timestamp(), size) + ③ current_hash != expected_hash? + → Err("⚠️ 文件已被外部修改(hash 不匹配),请重新读取") + 含 current_hash 让前端可选自动重读 + ④ hash 通过 → 执行 patch(L2 old_text 校验 + L3 .bak) +``` + +### 决策 5:截断策略 — 软删除标记(非真删) + +关联 UX-2025-09 编辑消息功能。patch_file 本身不涉及消息截断,但设计原则一致: + +``` +messages 表: status 列 + active — 正常显示 + truncated — 被编辑截断(前端不展示,后端不进 context) + 保留历史可追溯,与 WF-A soft_delete 模式一致 +``` + +--- + +## 四、三层防御架构 + +``` +┌─────────────────────────────────────────────┐ +│ L1: 文件级 Mutex │ +│ 防时机冲突:同文件读写自动串行化 │ +│ 实现: HashMap> (~15行) │ +│ │ +│ ┌───────────────────────────────────────┐ │ +│ │ L2: old_text 精确匹配 │ │ +│ │ 防内容错配:= 乐观锁(CAS) │ │ +│ │ 内容变了 → 匹配不上 → 报错 │ │ +│ │ 实现: 内容扫描 (~20行) │ │ +│ │ │ │ +│ │ ┌─────────────────────────────────┐ │ │ +│ │ │ L3: expected_hash 指纹校验 │ │ │ +│ │ │ 防版本漂移: 外部修改检测 │ │ │ +│ │ │ 实现: metadata() (~10行) │ │ │ +│ │ └─────────────────────────────────┘ │ │ +│ └───────────────────────────────────────┘ │ +│ │ +│ 底层兜底: FR-S7 .bak 备份(误操作可恢复) │ +└─────────────────────────────────────────────┘ +``` + +各层职责独立、互补: + +| 层 | 管 | 防什么 | 失效后果 | +|----|-----|--------|---------| +| L1 Mutex | 时机 | 两操作同时碰同一文件 | 数据丢失/半写 | +| L2 old_text | 内容 | 操作基于过时内容 | 错改别处 | +| L3 hash | 版本 | read→patch之间文件被外部改 | 基于错误版本操作 | +| .bak | 恢复 | 以上全失效时的最后防线 | 可回滚 | + +--- + +## 五、查找策略(line + old_text 组合) + +``` +有 line 参数? + ├─ YES → 跳到该行,检查周围是否包含 old_text + │ ├─ 匹配 → 替换(快速路径 ✅) + │ └─ 不匹配(行已偏移) → 降级: 全文扫描 old_text + └─ NO → 全文扫描 old_text + +全文扫描结果: + ├─ 0 处匹配 → Err("未找到目标文本,文件可能已被修改") + ├─ 1 处匹配 → 替换 ✅ + └─ N 处匹配(N>1) → 替换第 1 处 + warning("匹配到 N 处...") +``` + +--- + +## 六、边界情况处理 + +| 边界情况 | 行为 | 理由 | +|---------|------|------| +| 文件不存在 | Err("文件不存在") | 安全第一 | +| 文件 >1MB | warn + 继续或拒绝(复用 FR-S2 上限) | 防性能问题 | +| 二进制文件(含 \0) | Err("不支持二进制文件") | 文本操作不适用 | +| old_text 为空串 | Err("old_text 不能为空") | 防全文件匹配 | +| new_text == old_text | success + warning("无实际更改") | 不浪费 I/O | +| patches 为空 | Err("patches 不能为空") | 无意义调用 | +| 单次 patch 文件膨胀 >900% | warn(复用 FR-S7 逻辑) | 异常检测 | +| 目标路径是 .bak/.tmp | is_noise_file 过滤拒绝(CR-03) | 不改临时文件 | +| 路径含 ".." | validate_path 黑名单拦截 | 路径遍历防护 | +| RiskLevel | **Medium**(修改已有文件) | 比 write_file 同级(都是改文件) | + +--- + +## 七、性能分析 + +| 操作 | 开销 | 对比基准 | +|------|------|---------| +| Mutex 获取/释放 | ~100ns(非竞争)/ μs 级(等待) | 文件 I/O 是 ms 级,可忽略 | +| 全文扫描 old_text | O(n), n=行数(<1MB) | <1ms | +| hash 计算 | metadata() 一次系统调用 | ~μs 级 | +| .bak 备份 | 文件大小一次 copy | SSD ~100MB/s | +| **总开销** | | **<5ms(<< LLM 秒级延迟)** | + +--- + +## 八、与现有架构兼容性 + +| 维度 | 兼容性 | +|------|--------| +| tool_registry 注册 | ✅ 完全复用 write_file 模式 | +| RiskLevel 分流 | ✅ Medium → 审批(白名单收紧:纯读取外全审) | +| audit 审计日志 | ✅ process_tool_calls 自动入库 | +| validate_path | ✅ 复用黑名单 + canonicalize | +| .bak 备份 | ✅ 复用 FR-S7 已有逻辑 | +| ToolCard 前端渲染 | ✅ args 键值对自动适配 | +| 数据变更联动 AR-11 | ✅ emit df-data-changed 触发刷新 | +| 会话级授权 AE-04 | ✅ write_file 同类操作,可纳入 session_trust | + +--- + +## 九、实施步骤 + +### 第一批(核心三件套,~50 行后端 + 前端零改动) + +1. **`mod.rs` 或 `tool_registry.rs` 顶层**:加 `FILE_LOCKS` 静态 Mutex(~10 行) +2. **`tool_registry.rs`**:注册 `patch_file` handler(~40 行) + - 参数解析 + validate_path + - expected_hash 校验(可选,第一批可先加框架) + - 逐 patch 执行:扫描 old_text → 替换 → 记录结果 + - .bak 备份(复用 write_file 逻辑) + - 截断输出(stdout/stderr 各 10KB) +3. **测试**:AI 调用 `patch_file` 改一个已知文件,验证替换正确性 + +### 第二批(增强层,按需) + +4. before/after_text 锁定逻辑(~15 行) +5. expected_hash 指纹校验完整接入(~10 行) +6. read_file 返回值扩展 file_hash 字段(~5 行) + +### 第三批(远期) + +7. replace_all 开关 +8. regex 支持(old_regex 字段) +9. undo stack(会话内 patch 历史) + +--- + +## 十、替代方案否决记录 + +| 方案 | 否决理由 | +|------|---------| +| sed/awk via run_command | B-37 stdout 空;修好后也是间接操作,无原子性/备份/审计 | +| write_file 全量覆盖 | 已出事故(762→248字节);大文件 token 浪费;无 diff | +| git apply (Git-based patch) | 强依赖 git 仓库;非 git 目录不可用 | +| JSON Patch (RFC 6902) | 面向 JSON/结构化数据;不适合自由格式文本 | +| AST-level (tree-sitter) | 过度工程;需每语言 parser;AI 代码未必能 parse | +| 纯 line 号编辑 | 无内容校验=并发不安全;行号漂移易出错 | +| 全局文件队列 | 单用户桌面不需要;Mutex 够用且简单 10x | diff --git a/docs/02-架构设计/功能决策记录-2026-06-14.md b/docs/02-架构设计/功能决策记录-2026-06-14.md index becf076..e100cdc 100644 --- a/docs/02-架构设计/功能决策记录-2026-06-14.md +++ b/docs/02-架构设计/功能决策记录-2026-06-14.md @@ -322,6 +322,12 @@ - **治理方向**:① 后端支持阶段流转(planning→in_progress…);② 前端 map 对齐后端真实值(active/deleted/archived);③ 拆双字段(status 生命周期 + stage 开发阶段)。 - **状态**:📐 待治理(治标已落地,根本病根未除) +### i18n import 统一 `@/i18n` 路径别名 [2026-06-15] +- **决策**:vite.config.ts 加 `resolve.alias.{ '@': '/src' }` 路径别名;全项目 i18n import 统一为 `import i18n from '@/i18n'`,替代相对路径 `../i18n`/`../../i18n`。 +- **原因/取舍**:CR-08 i18n 批量改造时,workflow 代理将 stores/ 下层文件 i18n import 从相对路径(`../../i18n`)改为错误多层的 `../../../i18n` 或正确的 `@/i18n`(但项目无别名配置),导致 vite 构建失败(Could not resolve)。相对路径随文件深度变化易断(stores/ 两层 vs composables/ai/ 三层 vs utils/ 一层);`@/i18n` 绝对路径不随文件位置变化,零维护成本;Vue/Vite 生态标准做法(多数 Vue 项目默认配 `@` → src);别名仅影响构建时解析,运行时无开销。 +- **影响范围**:9 个源文件(import 侧) + 1 个配置文件(vite.config.ts)。 +- **状态**:✅ 已落地(commit d6eb855 + 6254d06) + ## 状态持久化 ### 窗口位置/大小:用 tauri-plugin-window-state(纯 Rust 层) @@ -481,6 +487,14 @@ - **「决策」术语边界**(2026-06-12):devflow 语境「决策/决策需求点」= 日常开发功能细节取舍(为什么这么定),**非** aichat 决策能力升级(B 路线 coordinator/conditions)。后者属架构层,记 Phase2计划/模块文档,不混入本文档。 - **代码审查甄别原则**(2026-06-13):审查发现问题时,按「运行时失败/数据损坏 → 简单清理 → 记录不动 → 不做」四档甄别。当前项目规模下,list_all 无 LIMIT、ALLOWED_COLUMNS 不分表、bool→int 重复等属「记录不动」——个人工具表不超千行,加分页/拆白名单是过度设计,维护成本 >> 收益。原则:**真实 bug 修、简单清理做、规模不到位的优化先不动**,保持全局简洁和扩展容易。 +## 文档维护 + +### 文档历史项保留原则:标状态不删行 [2026-06-15] +- **决策**:所有文档(ARCHITECTURE.md + 模块文档)中的历史设计项**一律保留原文不删除**,仅在行末或旁注标注实现状态:`✅ 已实现` / `❌ 未实现(设计预留)` / `⚠️ 骨架空壳(有文件但无实质逻辑)` / `~~已删~~`(R-PD-X 等重构决策引用)。 +- **原因/取舍**:全量核对报告(2026-06-15)发现 ARCHITECTURE.md 含多出虚构/过时项(ModelRouter 已删/Docker 等 5 节点未实现)。初版方案为「删虚构行+注」,用户两次明确否决删方案,要求保留全部历史项+标状态。理由:① 保留设计演进痕迹,接手方可理解"曾经考虑过什么、为什么没做";② 删除会导致核对报告等交叉引用断链;③ 标状态列比删行信息量更大。 +- **影响范围**:DOC-260615-01~14 全部文档修项均遵循此原则。已落地:ARCHITECTURE.md §5.4 ModelRouter 标 `❌ ~~已删~~` + §5.5 8 节点标 `✅` / `❌`(commit be38a44)。 +- **状态**:✅ 2026-06-15 落地(用户两次否决删方案后定稿,首批标注已 commit) + **相关文档**: - [Phase 1 架构决策](./Phase1架构决策-2026-06-12.md) — 架构级决策(ADR) - [经验记录](./经验记录-2026-06-14.md) — 踩坑/约定/技巧/bug 排查教训 diff --git a/docs/05-代码审查/定时走查-2026-06-15-P0复核.md b/docs/05-代码审查/定时走查-2026-06-15-P0复核.md new file mode 100644 index 0000000..fbd8a89 --- /dev/null +++ b/docs/05-代码审查/定时走查-2026-06-15-P0复核.md @@ -0,0 +1,85 @@ +# 定时代码走查 — P0 状态复核(2026-06-15) + +> 触发:定时走查 cron(每 30 分钟)。本轮重点 = 自上次走查(HEAD `4b5f096`)起新增内容走查 + 既有 P0 修复状态地面复核。 +> 方法:2 路后台代理(TaskDetail.vue 新文件 / 后端提交 `2de0c6e` 16 文件大改)+ 主代理地面核对(`project.ts:273` / `workflow.rs:211` / `useAiStream.ts` / `stream_recv.rs` / `todo.md`)。 +> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。 +> 关联:[工作区多角度走查-2026-06-15.md](./工作区多角度走查-2026-06-15.md)(上一轮,3 P0 + DRY 发现源头)。 + +--- + +## 核心结论:纠正过时认知(定时走查首要价值) + +| P0 | 上轮判断 | 本轮地面复核 | 处置 | +|---|---|---|---| +| **B-260615-34** selectType | 「未修,仍 camelCase」 | **已修 ✅** — `project.ts:273` 现为 `select_type: ... ?? 'single'` + 正确注释「Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211」;后端 `workflow.rs:211` 同 `select_type: Option`。前后端契约对齐。 | todo 标 `[x] ✅`,删过时描述 | +| **B-260615-32** 队列收尾 | 未修 | **仍未修 ❌** — `onStreamTimeout`(useAiStream.ts:24-53)复位 streaming/generatingConvId/currentText + clearStreamWatchdog + 反向扫 completed toolCall 区分文案 + push 错误消息,**仍不清 `state.queue`**(useAiSend:116 有 clearQueue 但收尾路径未调)→ 生成中输入消息丢失 | todo 加复核标注,仍 `[ ]` | +| **B-260615-33** 骨架屏 | 未修 | **仍未修 ❌** — `onStreamTimeout` 仍**不回滚 running toolCall**(只读 `status===completed` 做文案判断,不改 running→rejected)→ 审批卡片后端 hang 时永久骨架屏,无重审入口 | todo 加复核标注,仍 `[ ]` | + +> 「B-34 未修」是上一轮压缩前快照,之后提交(`80b9243`「P0 bug 修复」批)已修。后端代理通过后端 IPC 签名推断「前端已对齐」**结论正确**。定时走查纠正过时认知,避免「以为没修催着修」。 + +--- + +## 新发现 P0(后端代理 ②) + +### B-260615-35 — broadcast `Lagged` 兜底仅 warn,关键终态事件丢失时前端永久卡死 + +**位置**:`src-tauri/src/commands/workflow.rs:90-110`(forward 任务循环) + +**问题**:`run_workflow` 的 forward 任务靠 `finished = matches!(event, WorkflowCompleted | WorkflowFailed)` 判定退出。broadcast 滑动窗口 `Lagged(n)` 时丢 n 条最旧事件,代码注释**已识别风险**(「关键终态事件可能永久丢失」),但兜底措施只有 `tracing::warn!`,**没有主动从 DB 查终态或加 watchdog 超时**。若 Lagged 恰好丢了 WorkflowCompleted/Failed,forward 任务永远 `rx.recv().await` 等不到 finished,前端依赖事件关闭审批/完成弹窗的场景**永久卡死**,只能靠 DB 轮询兜底(实时性差)。 + +**修法(代理建议)**:`Err(RecvError::Lagged(n))` 分支后加被动超时/主动查 DB — 累计 lag 次数到阈值或单次 n 过大时,查 `WorkflowRepo::get_by_id(&forward_exec_id)` 看是否终态,若是则补 emit `workflow-event` 给前端并 break。需把 `state.db.clone()` 也 move 进 forward 任务(当前仅 executor spawn 持有 db)。 + +**优先级**:P0(低频但后果为「永久卡死」无自愈),开发者已知未做兜底。 + +--- + +## 新内容走查结果 + +### 1. TaskDetail.vue(362 行新文件)— DRY 复发,单文件质量良好 + +| # | 等级 | 发现 | +|---|---|---| +| ① | 🔴 | `.ai-md` 样式块**第 5 份重复**(CR-260615-09 复发):`:263-333` ~70 行与 ProjectDetail/Ideas/Knowledge 逐字符同,累计 ~350 行重复 CSS。TaskDetail 是抽公共样式(`src/styles/ai-md.css` 或 `composables/useMarkdownCss.ts`)最佳时机 | +| ② | 🔴 | `renderedDescription` computed **第 4 份重复**(CR-260615-10 复发):`:116-119` `void mdReady.value` 技巧四处重复。建议 `useMarkdown` 增 `renderedRef()` 帮手收敛 | +| ③-⑦ | 🟡 | 路由 id 空守 / 快切竞态(无序号/AbortController)/ 错误信息缺 `.message` 优先 / `projectApi.list()` 全量拉取仅解名 / 静态文案半 i18n 半硬编码 | +| ✅ 亮点 | — | XSS(DOMPurify + escapeFallback 双层)/ `void mdReady` 响应式依赖捕获(注释明确 B-24 修复点)/ Promise.all 并发 / onMounted 不阻塞 load | + +### 2. 后端提交 `2de0c6e`(16 文件 1418+/152-)— 工程化重构扎实,1 真实 P0 + 文档完善项 + +| # | 等级 | 位置 | 发现 | +|---|---|---|---| +| ① | 🔴 | stream_recv.rs:97-108(2de0c6e 版) | 注释虚假承诺「stop_notify 即时打断」,实际该提交无 stop_notify 分支。**working tree 已修** — `80b9243` 补了本地 Notify + 第 3 select 分支(:102-154 现状),无需记 todo | +| ② | 🔴 | workflow.rs:90-110 | **见 B-260615-35**(broadcast Lagged 终态丢失) | +| ③ | 🟡 | stream_recv.rs:60-69 | `"timed out"` 分支**非死代码**(代理自查纠正初判:`"operation timed out"` 不含子串 `"timeout"`),可合并到首分支提升可读性 | +| ④ | 🟡 | commands.rs:265-287 | `ai_chat_stop` 3s 兜底任务用闭包前快照 conv_id,会话切换后 emit 错对话(spawn 内重新读 active_conversation_id 配合 generating 判定) | +| ⑤ | 🟡 | commands.rs:367/313/452 | tauri async cmd 内同步调 keyring IO 占 runtime worker,建议 `spawn_blocking`(Linux secret-service D-Bus 慢时影响明显) | +| ⑥ | 🟡 | commands.rs:313-322 | `ai_list_providers` mask 后 api_key 字段语义模糊(`""`/`"sk-xxx"`/`"sk-1••••xyz9"` 三语义混一字段),建议拆 `has_api_key: bool` + `api_key_mask` | +| ⑦ | 🟡 | secret.rs:51-64 | failcount sidecar 锚 `current_dir()` 不稳定(跨启动 cwd 变化致计数失效),建议改 `app_data_dir`(同 db_path) | +| ⑧ | ⚪ | Cargo.toml + secret.rs | keyring noop backend 自检缺失,Linux 漏 feature 时 set 静默成功 get 永空 → 兜底误判「已迁移」清 DB api_key 致密钥永久丢失 | +| ⑨ | ⚪ | agentic.rs:317-361 | 达 MAX_AGENT_ITERATIONS 后先 emit AiError 再 emit AiCompleted,前端 UX 处理依赖实现 | +| ⑩ | ⚪ | workflow.rs:213 | `let _ = &app;` 显式占位,直接删 app 参数更清晰(Tauri 自动重生成路由) | +| ✅ 亮点 | — | B-09 GeneratingGuard RAII 收敛复位(Drop 兜底 panic/异常路径)/ B-11 stale loop 一致性校验(会话切换旧 loop 自动退出)/ stream_recv 错误诊断体系(char 迭代修 UTF-8 边界 panic + 11 单测)/ secret.rs keyring 优先+DB 兜底双源 / tool_registry 双层校验防 symlink 逃逸 + 单次 open 消 TOCTOU | + +--- + +## 其他确认 + +- **aiShared.ts 破环 ✅ 亮点**(ARC-260615-06 落地):`nextMsgId` 下沉 aiShared,useAiStream 注释明确「本模块不再 import useAiEvents,环消除」。4 composable 共享同一计数器,边界严格(仅下沉「无依赖、被多 composable 共用」成员)。 +- **stream_recv.rs working tree 已补 stop_notify ✅**(:102-154 三分支 select! 含 stop_notify 即时打断)。2de0c6e 提交自带的注释虚假承诺问题,已被后续 `80b9243` 修代码侧,**当前 working tree OK,无需记 todo**。 + +--- + +## 📊 本轮摘要 + +| 类别 | 数 | 代表 | +|---|---|---| +| P0 状态复核 | 3 | B-34 已修 ✅(纠正过时)/ B-32 未修 / B-33 未修 | +| 新发现 P0 | 1 | B-260615-35 broadcast Lagged 终态丢失 | +| 🔴 DRY 复发 | 2 | CR-09(四→五份)/ CR-10(第四份)| +| 🟡 建议 | 12 | TaskDetail 5 + 后端 5 + stream_recv 1 + workflow 1 | +| ⚪ 可选 | 4 | 后端 3 + TaskDetail 1 | +| ✅ 亮点 | 4 | aiShared 破环 / GeneratingGuard RAII / stale loop 校验 / stream_recv 错误诊断 | + +**本轮价值**:纠正 1 个过时 P0 判断(B-34 已修)、坐实 2 个 P0 仍未修(B-32/33)、新增 1 个真实 P0(B-35)、确认 1 个架构亮点落地(aiShared 破环)。无运行时 panic/数据损坏/并发竞争类硬伤,后端重构质量评级**良**。 + +todo 映射:B-260615-35(新增);CR-260615-09 描述更新(四→五份);B-260615-34 标 ✅;B-260615-32/33 加复核标注。 diff --git a/docs/05-代码审查/定时走查-2026-06-15-第2轮.md b/docs/05-代码审查/定时走查-2026-06-15-第2轮.md new file mode 100644 index 0000000..1973682 --- /dev/null +++ b/docs/05-代码审查/定时走查-2026-06-15-第2轮.md @@ -0,0 +1,123 @@ +# 定时代码走查 — 第 2 轮(2026-06-15) + +> 触发:定时走查 cron(每 30 分钟)。本轮范围 = 自上次走查起新增 6 提交(`fddca9d`/`892a642`/`19d64fc`/`d809cf4`/`8710d6c`/`06a2dea`)前端改动 + 工作区未提交(`types.ts`/`project.ts` M)。 +> 方法:3 路后台代理并行(A:ARC-05 store 拆分 / B:B-22 状态同步含 B-32/33 核对 / C:CR-11 健壮性+工作区未提交)+ 主代理地面核对(`useAiStream.ts`/`useAiSend.ts`/`workflow.rs`/`f93b758` 提交)。 +> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。 +> 关联:[定时走查-2026-06-15-P0复核.md](./定时走查-2026-06-15-P0复核.md)(第 1 轮,B-32/33 结论被本轮纠正)。 + +--- + +## 核心结论:3 前端 P0 全修(第 3 次纠正过时认知) + +| P0 | 第1轮判断 | 第2轮地面复核 | 提交 | 处置 | +|---|---|---|---|---| +| **B-34** selectType | 已修 ✅ | 已修 ✅(确认) | — | 保持 ✅ | +| **B-32** 队列收尾 | 「未修」 | **已修 ✅** | `f93b758` | todo 标 ✅,纠正第1轮 | +| **B-33** 骨架屏 | 「未修」 | **已修 ✅** | `f93b758` | todo 标 ✅,纠正第1轮 | + +**B-32/B-33 地面证据**(`useAiStream.ts` 当前 81 行,第1轮 Read 时为 73 行旧版): +- `:28` `state.queue = [] // B-32:超时收尾同步清队列` + useAiEvents.ts:225(AiError) + useAiSend.ts:152(stopChat)/124(approveToolCall catch) **四路径全清队列** +- `:43-45` onStreamTimeout 单遍反向扫描 running toolCall → rejected(合并探测 completed 为单次 O(n),实现干净) + +**B-32/B-33 修复提交**:`f93b758`「修复: B-32+33 流式收尾清队列+回滚 running toolCall」3 文件 +18/-7。 + +> 第1轮「B-32/33 未修」为过时快照(working tree 当时旧版,与 B-34 同根因)。**定时走查连续 3 轮纠正过时 P0 判断**(B-34 → B-32 → B-33),说明走查必须以地面 Read 为准,不可信赖记忆/旧快照。前端 3 P0 至此全部闭环。 + +--- + +## 新发现 P0:AR-11 前端 listener 永不 attach(功能半接通死代码) + +**位置**:`src/stores/project.ts:31-55`(定义)+ `:109-110`(export)+ `src/App.vue:203-213`(onMounted 未挂载) + +**根因**:工作区未提交改动新增 `startDataChangedListener`/`stopDataChangedListener` 两函数 + export,但**全项目零调用方**(grep 确认)。后端 `emit_data_changed`(audit.rs:259-270)+ 自动执行路径(audit.rs:351)+ 审批路径(commands.rs:175)三处 emit 已就位且逻辑正确,事件 `df-data-changed` 正常发出,但前端 `listen('df-data-changed', …)` 永不执行 — 监听器从未 attach。 + +**影响**:AR-11「数据变更联动刷新」功能**完全失效**。用户通过 AI 工具(create/update/delete project/task/idea)改数据后,列表不自动刷新,仍需手动刷新(正是 AR-11 要消除的痛点)。后端 emit 成死事件。 + +**修复方向**:`App.vue` onMounted 调 `await projectStore.startDataChangedListener()`,onUnmounted 调 `stopDataChangedListener()`(对齐 `ProjectDetail.vue:463/470` 的 workflow listener 生命周期模式,数据变更可能来自任意页面)。 + +**性质**:工作区未提交半成品(非历史遗留,非 ARC-05 拆分收尾)。代理 C 确认 diff 是纯 AR-11 新增。 + +> 注:todo:77 AR-11 条目原标「暂缓」,第2轮复核更正为「后端已实施 ✅,前端半接通 🔴」。 + +--- + +## ARC-05 上帝 store 拆分 — 质量评级:优 + +**提交** `8710d6c`:`project.ts` 362→barrel + 5 子 store(state/projects/tasks/ideas/workflow)。 + +**3 项硬指标全部兑现**: +| 指标 | 证据 | +|---|---| +| barrel 零改动兼容 | 逐字段比对 8710d6c~1 与 8710d6c 的 `return reactive({...})`,37 key 完全一致(8 getter state + clearError + 9 project + 4 task + 6 idea + 7 workflow actions + pendingApproval/stats computed)。state getter 用 `get projects() { return state.projects }` 保留「避免 loadXxx 重赋值后视图为空」关键设计 | +| state 真单例 | state.ts 模块级 `export const state = reactive({...})`,ESM 保证全 app 单实例,4 子 store import 同一引用 | +| 依赖图无环 | 4 子 store 互不 import,只依赖叶子 `./state`;barrel 单向依赖子 store,无循环/无初始化顺序陷阱 | + +**🟡 建议 3**: +1. `state.ts:32-37` 可变 `let _eventUnlisten` 跨模块共享 + setter,依赖 ESM live-binding(运行时正确但反直觉),建议注释点明或并入 reactive +2. **AR-11 监听器已侵入 barrel**(working tree project.ts:31-55),违反「纯 barrel」定位 — 建议抽第 6 子 store `project/dataChange.ts`(与上面 AR-11 P0 同源) +3. `projects.ts:105` clearError 夹在 projects return 语义错位(属 state 层),建议移除(barrel 已独立从 state 导入) + +**⚪ 可选 2**:注释「四子 store」实为 5 文件(四领域+共享 state)/ createXxx 入参类型内联可抽 types。 + +**✅ 亮点**:越层 invoke 下沉彻底(`approve_human_approval`/`cancel_workflow_node` 下沉 `api/workflow.ts:39-54`,B-34 snake_case 对齐在 api 层统一)/ ProjectStore 类型 + 单例 `_storeInstance` 保留 / view 端 7 处 `useProjectStore` 零改动。 + +--- + +## B-22 前后端状态同步 — 实现质量良好 + +**提交** `d809cf4`:`ai_is_generating` IPC(commands.rs:12)+ sendMessage 预检(useAiSend.ts:27)。 + +**设计对症**:双源(前端 streaming / 后端 generating)各自维护确有不同步风险,发送前查后端真值对症。 + +**竞态窗口可接受**:`useAiSend.ts:42` 查后端与 `:97` 实际 sendMessage 之间有窗口,但后端 `ai_chat_send:57` 原子检查+占用(`if session.generating { Err }` + `generating=true` 同锁内)是真兜底。前端预检仅优化 UX(提前入队而非发出去被拒),降级路径完备(IPC 失败 catch 退化为原 streaming 预检)。 + +**回归安全**:正常事件流(delta/tool/AiAgentRound/AiCompleted/AiError)未改,仅 sendMessage 入口前置预检,不影响 handleEvent。 + +**🟡 Issue 1(低)**:`useAiSend.ts:53-57` 入队分支复位 `streaming=true` 但**未启动看门狗**(resetStreamWatchdog 只在 :89 正常发送路径调)。极端卡死场景(后端既不 emit、Drop spawn 又未执行)streaming 可能永真。建议入队分支也调一次 resetStreamWatchdog,代价一行。非 d809cf4 新引入回归(onStreamTimeout 本就依赖正常路径启动的看门狗)。 + +**信息 Issue 2**:`useAiSend.ts:60-61` 注释「streaming=true 但后端 false」描述与实际复位方向(onStreamTimeout/AiError 复位 streaming=false)有歧义,建议改注释。 + +--- + +## CR-11 健壮性批(fddca9d)— ✅5 亮点 + 1 死逻辑副产品 + +**子项 ⑦⑧⑨⑩⑪⑫ 全部实施**: + +**✅ 5 亮点**: +1. `useAiConversations.ts:77-93` JSON.parse 逐条 try/catch(原单条坏 args 清空整对话 → 单条降级空对象) +2. `useAiEvents.ts:130-133` AiHeartbeat 显式 case 防 TS 穷举穿透 +3. `useAiSend.ts:84-85` 复用 findToolCall(消除内联 flatMap+find 重复 + 反向扫描 O(1) 均) +4. `ToolCard.vue:236-242` argString 去 3 处 `as any`,`Record` 类型收窄 +5. `workflow.ts:44-92` approveHumanApproval 签名收敛 `(decisions: string[], comment?)`(消除单/多选调用方歧义) + +**⚠️ ⑪ 实施引入死逻辑(CR-260615-18)**:`workflow.ts:67-68` `decision = selectType === 'multiple' ? decisions[0] ?? '' : decisions[0] ?? ''` 两分支返回值完全相同,三元无意义。后端 workflow.rs:291-296 有兜底。修:直接 `const decision = decisions[0] ?? ''`。 + +**🟡 其他**:CR-19(action 字段 emit 但前端不消费,契约冗余)/ CR-20(stopDataChangedListener try/catch 过度防御,与 workflow.ts:108 不一致)。 + +--- + +## P0 汇总(仍未修) + +| ID | 问题 | 状态 | +|---|---|---| +| B-260615-35 | broadcast Lagged 兜底仅 warn → 终态事件丢失前端永久卡死(后端 workflow.rs:90-110) | **未修**(第1轮发现,本轮确认仍在)| +| AR-11 前端 | listener 永不 attach(App.vue 未挂载) | **未修**(本轮新发现,工作区半接通)| + +--- + +## 📊 本轮摘要 + +| 类别 | 数 | 代表 | +|---|---|---| +| P0 全修确认 | 3 | B-34(保持✅)/ B-32 / B-33(第1轮过时,本轮纠正)| +| 新发现 P0 | 1 | AR-11 前端 listener 永不 attach(功能半接通)| +| 仍存 P0 | 2 | B-35 broadcast Lagged(后端)/ AR-11 前端 attach | +| ✅ 质量肯定 | 2 | ARC-05 store 拆分(优)/ CR-11 健壮性批(5 亮点)| +| 🟡 建议 | 6 | ARC-05 三项 / B-22 看门狗 / CR-18 死逻辑 / CR-19·20 | +| ⚪ 可选 | 4 | ARC-05 两项 / ToolCard 兜底 | + +**本轮价值**:第 3 次纠正过时 P0 判断(B-32/33 实已修,f93b758),前端 3 P0 全闭环;新发现 AR-11 前端 listener 永不 attach(工作区半接通死代码,功能未闭环);肯定 ARC-05 拆分(3 硬指标全兑现)与 CR-11 健壮性批质量。 + +**方法论警示**:连续 3 轮纠正过时判断(B-34→B-32→B-33),根因是第1轮 Read 到 working tree 旧版(73 行)而 HEAD 已是新版(81 行)。**走查必须每次地面 Read,不可信赖上轮快照**。后续走查代理 prompt 应强调「以当前 working tree + HEAD 为准,git log 看提交,勿用记忆」。 + +todo 映射:B-32/33 标 ✅ + 复核更正 / AR-11(todo:77)状态更新 / CR-11(todo:212)标 ✅ / 新增 CR-260615-18/19/20。 diff --git a/docs/05-代码审查/定时走查-2026-06-15-第3轮.md b/docs/05-代码审查/定时走查-2026-06-15-第3轮.md new file mode 100644 index 0000000..1e133f4 --- /dev/null +++ b/docs/05-代码审查/定时走查-2026-06-15-第3轮.md @@ -0,0 +1,70 @@ +# 定时代码走查 — 第 3 轮(2026-06-15) + +> 触发:定时走查 cron(每 30 分钟)。范围 = 自第2轮起新增 3 提交(`dc27e79` AR-11 / `b08adcb` B-35 / `f574562` CR-18·19),工作区 src/ 干净。 +> 方法:主代理地面 git show 3 提交 + Read 关键文件核对修复质量/回归(修复集中、范围明确,无需派代理)。 +> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。 +> 关联:[第1轮(P0复核)](./定时走查-2026-06-15-P0复核.md)、[第2轮](./定时走查-2026-06-15-第2轮.md)。 + +--- + +## 核心结论:走查价值闭环 + +接收方(另一会话)据第2轮走查修复了全部发现项 + 核对撤销 1 误判: + +| 项 | 发现轮 | 第3轮核对 | 提交 | +|---|---|---|---| +| AR-11 listener 永不 attach | 第2轮 🔴 P0 | ✅ **已修**(App.vue:218 attach) | dc27e79 | +| B-35 broadcast Lagged 兜底 | 第1轮 🔴 P0 | ✅ **已修**(分支完整健壮) | b08adcb | +| CR-18 decision 死逻辑 | 第2轮 🟡 P2 | ✅ **已修**(删三元) | f574562 | +| CR-19 action 注释 | 第2轮 🟡 P3 | ✅ **已修**(注释明确,保留预留) | f574562 | +| CR-20 stop try/catch 过度防御 | 第2轮 🟡 P3 | ✅ **撤销(走查误判)**:接收方核 workflow.ts:108 一致 | f574562 commit 说明 | + +> **前端 P0 全闭环**:B-32 / B-33 / B-34 / B-35 / AR-11 全部已修。 +> **走查↔接收方双向校验有效**:接收方不仅修复,还核对出 CR-20 为走查误判(workflow.ts:108 stopEventListener 确有 try/catch,写法一致),主动撤销 — 说明走查质量经得起反向核对。 + +--- + +## 各修复核对 + +### AR-11 listener attach(dc27e79)✅ +- `App.vue:218` onMounted `await projectStore.startDataChangedListener()` + `:258` onUnmounted `projectStore.stopDataChangedListener?.()` +- barrel `listen('df-data-changed')` 按 entity 调 loadProjects/loadTasks/loadIdeas +- 后端 audit.rs `data_change_for_tool` 映射(create_project/idea/task→create,update_*→update,delete_*/purge→delete,restore/bind_directory→update)+ emit_data_changed + commands.rs ai_approve 成功路径 emit +- listener 单例(`if (_dataChangedUnlisten) return`)防重复 attach ✅ +- **功能闭环**:AI 工具 create/update/delete 后前端列表自动刷新 + +### B-35 broadcast Lagged 兜底(b08adcb)✅ +- `workflow.rs` `forward_db = state.db.clone()` move 进 forward 任务(第1轮建议) +- `lagged_total` 累计达 `LAGGED_PROBE_THRESHOLD=8` 查 `WorkflowRepo::get_by_id` 终态 +- completed/failed/cancelled 补 emit workflow-event + break;合成事件 total_duration_ms/failed_node 占位(注释说明 broadcast 不暴露原字段,DB 为准) +- **分支完整健壮**:Ok(Some) 终态→break / running→重置 lagged_total 继续等 / Ok(None)→warn 重置等 / Err(DB)→warn 重置等 / Closed→break +- 第1轮建议方案原样落地 + +### CR-18/19/20(f574562)✅ +- **CR-18**:`workflow.ts:67` decision 三元删除 → `const decision = decisions[0] ?? ''` +- **CR-19**:action 注释明确「为未来 delete 本地移除等差异化刷新预留,当前未消费统一 load 全量」 +- **CR-20 撤销**:提交说明「经核查与 workflow.ts:108 stopEventListener 写法一致(走查误判,不修)」 + +--- + +## 新发现(1 轻量) + +### CR-260615-21 🟡 P3 — App.vue:218 await 无 try/catch +`App.vue:218` `await projectStore.startDataChangedListener()` 无 try/catch。Tauri `listen` 失败(极罕见)会 reject onMounted。虽已是 onMounted 末尾(前面 appSettings/migrate/theme/i18n/keydown/loadCandidates 已跑,app 不崩),但 AR-11 静默失效 + Vue 控制台 warn。修:包 `try { await ... } catch(e){ console.error(...) }`。非阻塞,极低概率。 + +--- + +## 📊 本轮摘要 + +| 类别 | 数 | 代表 | +|---|---|---| +| 价值闭环(修复确认) | 4 | AR-11 / B-35 / CR-18 / CR-19 全到位 | +| 撤销误判 | 1 | CR-20(接收方核对 workflow.ts:108 一致)| +| 前端 P0 全闭环 | 5 | B-32/33/34/35/AR-11 全修 | +| 新发现 | 1 | CR-21 App.vue try/catch(P3 轻量)| + +**本轮价值**:确认前两轮走查发现被接收方完整修复(含 1 项撤销误判),前端 P0 全闭环。走查↔接收方双向校验机制有效(接收方主动核对出 CR-20 误判并撤销)。本轮仅 1 项 P3 轻量新发现,代码质量趋稳。 + +**方法论肯定**:第3轮严格执行「地面 git show + Read 核对」,未重复走查已审内容(前两轮已覆盖的 ARC-05/B-22/CR-11 主体不再重审,只核对修复),符合「跳过已审避免重复」。 + +todo 映射:B-35/CR-18/19/20/AR-11 标 ✅(CR-20 标撤销误判)+ CR-21 新增。 diff --git a/docs/05-代码审查/定时走查-2026-06-15-第4轮.md b/docs/05-代码审查/定时走查-2026-06-15-第4轮.md new file mode 100644 index 0000000..8cb6fb5 --- /dev/null +++ b/docs/05-代码审查/定时走查-2026-06-15-第4轮.md @@ -0,0 +1,89 @@ +# 定时代码走查 — 第 4 轮(2026-06-15) + +> 触发:定时走查 cron(每 30 分钟)。范围 = 自第3轮起 1 新提交(`94cdcdc` CR-21 修复)+ **i18n 垂直切片走查**(src/i18n 26 文件此前从未深查,最大未审区域)。 +> 方法:主代理地面 git show 核 CR-21 + 1 路后台代理走查 i18n 完整性/硬编码遗漏(45 tool_uses 全量扫)。 +> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。 +> 关联:[第1轮](./定时走查-2026-06-15-P0复核.md)/[第2轮](./定时走查-2026-06-15-第2轮.md)/[第3轮](./定时走查-2026-06-15-第3轮.md)。 + +--- + +## 一、CR-21 修复确认(价值第 4 次闭环) + +`94cdcdc` App.vue:218-222 `try { await startDataChangedListener() } catch(e){ console.error(...) }`。提交说明「cron 巡检捕获另一会话走查第3轮 CR-21」。✅ 已修。 + +--- + +## 二、i18n key 树质量 — 优秀 ✅ + +**466 key 全对齐**(12 命名空间 × 2 语言): + +| 维度 | 结果 | +|---|---| +| zh vs en key 树 | **0 缺 key / 0 多余 / 0 结构不一致** | +| 翻译质量 | 0 TODO/占位符,0 复制中文漏译(仅 `aiLanguageZh=🇨🇳 简体中文` 1 处合理) | +| 插值参数 | `{var}` 占位符 zh/en 逐一匹配,0 mismatch | +| 命名/嵌套 | 顶层=文件名,camelCase,嵌套≤2 层,一致 | + +> i18n 文件本身无需改动。问题全在 **src/ 硬编码遗漏**。 + +--- + +## 三、硬编码遗漏 — 40+ 处(CR-08 扩展,真实问题) + +CR-260615-08 原记 3 处,i18n 代理全量扫发现 **40+ 处**,按严重度分三档: + +### 🔴 P1 — 直接破坏 i18n(已有 key 却硬编码) + +**P1-1 `useAiEvents.ts:182`(最高优先,改一行)**:`tc.result = '用户拒绝了此操作'` 硬编码,绕过已有 `aiTool.rejectedHint`(en=`User rejected this action`)。ToolCard.vue:54 渲染 `tc.result || $t('aiTool.rejectedHint')`,`||` 短路致 **en locale 拒绝提示恒中文**,已有 en 翻译被一行废掉。修:删该赋值(让 ToolCard 走 t())或改 `t('aiTool.rejectedHint')`。 + +**P1-2 `TaskDetail.vue` 整页 13 处未接 i18n**:L6/12/17/24 + 11 个 label span + L144 errorMsg。i18n 无 taskDetail namespace(只有 tasks),需新建。en locale 全中文。 + +**P1-3 `ProjectDetail.vue` 审批/导入 5 处**:L12 导入目录 / L201 确认(N) / L220 取消 / L392 confirmDialog / L399 Message.success / L404 Message.error / L381 未知错误 fallback。 + +**P1-4 `Dashboard.vue:76`** `暂无项目,去创建一个`。 + +**P1-5 `AiChat.vue` 3 处**:L107 title=清空 / L471 confirmDialog / L687 showToast 发送失败。 + +### 🟡 P2 — JS 字符串赋 UI/Toast + +**P2-1 `ToolCard.vue:152-164` ARG_LABEL_MAP**:11 个审批参数标签硬编码(ID/项目 ID/名称/标题/描述/字段/新值/路径/优先级/标签/来源),aiTool.ts 无对应 key,需新增 `aiTool.argLabels.*`。 + +**P2-2 store error fallback 11 处全中文**:knowledge.ts:53/64/74/105 + ideas.ts:10/20/38 + projects.ts:13/25/37/63/71/81/90 + tasks.ts:10/20/38 + workflow.ts:19。en locale 下所有加载/CRUD 失败 toast 中文。 + +**P2-3 `useAiStream.ts:52-53`**(CR-08 行号偏差,原记 39-41):看门狗超时两文案 `⚠ 工具已执行完成...` / `⚠ 响应中断...` 直接 push messages。 + +**P2-4 `useAiSend.ts:49/65/130`**:队列满 throw + 审批未送达 tc.result。 + +### ⚪ P3 — 边界/低优先 + +**P3-1 `utils/time.ts:46-51`(隐蔽)**:`formatRelative` 相对时间硬编码(刚刚/X 分钟前...)。i18n 已有 `common.justNow/minutesAgo/hoursAgo/dayAgo` key 却没用 → en locale 所有时间戳中文。 + +**P3-2 `router/index.ts:18-60`**:8 个 `meta.title` 中文,但 **0 消费方**(grep meta.title/document.title 无匹配),死代码/潜伏隐患。 + +**P3-4 `i18n/index.ts:20` `fallbackLocale: 'en'`**:zh-CN 用户缺 key 回退英文,反直觉(当前 key 全对齐不触发,设计气味)。 + +--- + +## 四、亮点 + +- **i18n key 树工程化扎实**:466 key 双语全对齐、插值参数一致、命名规范、无占位符 — 国际化基础设施质量高。 +- **i18n index 初帧渲染处理合理**:appSettings 异步,`getInitialLocale()` 默认 zh-CN,loadAll 后回填,已知短暂态有注释。 +- **TaskDetail.vue 单文件 XSS/响应式到位**(第1轮已肯定),i18n 是唯一短板。 + +--- + +## 📊 本轮摘要 + +| 类别 | 数 | 代表 | +|---|---|---| +| 价值闭环 | 1 | CR-21 → 94cdcdc ✅ | +| i18n key 树肯定 | 466 | 全对齐 0 缺 key(优秀)| +| 硬编码新发现 | 40+ | P1-1 useAiEvents:182(改一行最高 ROI)/ TaskDetail 整页 / ToolCard ARG_LABEL_MAP / store fallback 11 处 / time.ts 隐蔽 | +| P1 i18n 破坏 | 5 | useAiEvents:182 / TaskDetail / ProjectDetail / Dashboard / AiChat | +| P2/P3 | 6 | ARG_LABEL_MAP / store fallback / useAiSend / time.ts / router meta / fallbackLocale | + +**本轮价值**:i18n 垂直切片暴露真实问题 — key 树优秀但 src/ 硬编码 40+ 处(CR-08 严重低估)。**P1-1 useAiEvents:182 是明确 i18n 破坏 bug**(一行废掉 en 翻译),改一行最高 ROI,应优先。TaskDetail 整页未接 i18n(13 处)是新页面 i18n 债。 + +**方法论**:换垂直切片角度(i18n 维度)有效突破前几轮按提交/改动的横切盲区,发现 40+ 处硬编码 — 印证 task2/3「功能/模块垂直走查」的价值(换角度有新发现)。 + +todo 映射:CR-260615-08 扩展(3→40+ 子项,标 P1-1 最高优先)+ CR-21 标 ✅。 diff --git a/docs/05-代码审查/定时走查-2026-06-15-第5轮.md b/docs/05-代码审查/定时走查-2026-06-15-第5轮.md new file mode 100644 index 0000000..411020b --- /dev/null +++ b/docs/05-代码审查/定时走查-2026-06-15-第5轮.md @@ -0,0 +1,94 @@ +# 定时代码走查 — 第 5 轮(2026-06-15) + +> 触发:定时走查 cron(每 30 分钟)。范围 = **api 层垂直切片**(9 文件,此前从未深查,IPC 契约边界)。无新提交(自第4轮起 src/ 干净)。 +> 方法:2 路后台代理(A:api invoke 契约层 / B:types 类型对齐层)+ 主代理地面核对 + **WebSearch 权威裁决**。 +> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 待修项见 `docs/todo.md`。 +> 关联:[第1-4轮](./)。 + +--- + +## 🚨 核心发现:Tauri v2 camelCase 行为权威裁决(代理矛盾 → 纠正假阳性) + +### 矛盾 +- **代理 A**:Tauri v2 **不转** camelCase,6 处 IPC 参数遗漏(ai_approve/ai_save_provider/knowledge_events/ai_set_concurrency_config/ai_set_provider/ai_pending_tool_calls)真 P0(B-34 同类) +- **代理 B**:Tauri v2 **默认转** camelCase(宏生成别名),6 处全误判 + +### 权威裁决(WebSearch Tauri v2 官方文档 + StackOverflow) + +> **Tauri v2 默认自动转换 snake_case(Rust)↔ camelCase(JavaScript)**。 +> Rust `#[tauri::command] fn greet(tool_call_id: String)` ↔ JS `invoke('greet', { toolCallId })` 自动匹配。 +> **除非** `#[tauri::command(rename_all = "snake_case")]` 才需 JS 传 snake_case。 + +项目地面核对:`grep rename_all src-tauri/` 仅 `state.rs:30`(`KnowledgeConfig` 的 serde,**非 command 参数**),**无任何 `#[tauri::command]` 用 rename_all** → 全部走默认(camelCase 转换)。 + +### 结论 + +**代理 B 对,代理 A 6 处全误判(假阳性)**: +- `api/ai.ts:15` `{ toolCallId }` → 后端 `tool_call_id` ✅ 自动转换,工作 +- `api/ai.ts:20` `{ convId }` / `:47-54` `{ providerType/baseUrl/apiKey/defaultModel }` / `:59/64` `{ providerId }` / `:69` `{ globalLimit/perConvLimit }` → 全 ✅ +- `api/knowledge.ts:80` `{ knowledgeId/eventType }` → ✅ + +camelCase 和 snake_case **都工作**(项目混用:ai.ts 用 camelCase,workflow.ts 用 snake_case,均正常)。 + +--- + +## 🔑 根因:B-34 错误注释传播误导 + +**`src/stores/project.ts:273`** B-34 注释「Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211」**与权威矛盾**(实际转)。 + +- 代理 A 据此错误注释判 6 处假阳性 P0(认为 camelCase 失效) +- B-34 实际「修复」(selectType→select_type)是 **no-op**:camelCase 本工作,snake_case 也工作,两种都接受。改了无害,但**注释错误传播误导** +- 若「多选审批静默失效」曾为真症状,根因**非 selectType 命名**,需复核(camelCase 转换正常) + +> **本轮最高价值**:揭露 B-34 技术债(错误注释),避免后续维护者/走查代理据此做无用「修复」(代理 A 已踩坑)。 + +--- + +## 代理 B 发现(types 对齐)— 良好,4 项低风险 + +**字段名/可选/枚举/响应包装全对齐**(无 P0 undefined 风险):7 Record struct(Idea/Project/Task/Workflow/AiProvider/Knowledge/KnowledgeEvent)字段名全 snake_case 对齐,Option↔可选对齐,AiChatEvent/WorkflowEvent union 变体对齐。 + +**4 项低风险**(CR-260615-22): +1. `TaskRecord.status`(types.ts:88)注释 `review_ready/merged/abandoned` **错**,后端 TaskStatus(types.rs:165)= `todo/in_progress/in_review/testing/done/blocked/cancelled` +2. `IdeaRecord.status`(:11)注释漏 `archived`(IdeaStatus 6 变体) +3. `ProjectRecord.status`(:39)注释漏 `testing/releasing`(ProjectStatus 7 变体) +4. `AiConversationSummary.models`(:233)冗余(后端返但前端无消费点) + +> status 字段类型是 `string`(非 literal union),注释不阻断运行时,仅误导维护者。 + +--- + +## ✅ 亮点 + +- **api/types.ts 类型对齐质量高**:7 Record struct + union 类型全对齐,无字段漂移 +- **IPC 命令名零漂移**(代理 A 核 60+ 命令注册):无拼写错/未注册/已删残留 +- **越层下沉治理**:store 不直碰 invoke(workflow.ts:7-8 注释),api 是唯一 IPC 边界 +- **workflow.ts 契约自觉**:虽「不转 camelCase」判断有误,但 snake_case 风格统一 + options 透传注释(R-PD-5)是好的防回归实践 + +--- + +## 📊 本轮摘要 + +| 类别 | 数 | 代表 | +|---|---|---| +| 权威裁决 | 1 | Tauri v2 默认转 camelCase(代理 B 对)| +| 假阳性纠正 | 6 | 代理 A IPC camelCase 遗漏全误判 | +| 根因澄清 | 1 | B-34 错误注释传播误导(CR-23)| +| 低风险新发现 | 4 | types 枚举注释错/漏 + 冗余字段(CR-22)| + +**本轮价值**:多代理交叉验证暴露矛盾 → WebSearch 权威裁决 → 纠正 6 处假阳性(避免后续基于错误注释的无用修复)+ 揭露 B-34 注释技术债。 + +**方法论**: +1. **走查须以权威行为为准(官方文档/实际运行),非代码注释** — 注释可能错误(如 B-34),代理 A 据错误注释判 6 处假阳性 +2. **多代理交叉验证有效** — 代理 A/B 结论矛盾触发权威裁决,单代理走查无此纠错机制 +3. **api 层垂直切片证实类型对齐质量高**(与前几轮横切互补) + +todo 映射:**不记代理 A 6 处**(假阳性)+ CR-260615-22(types 注释/冗余 P3)+ CR-260615-23(B-34 注释修正 P2)。 + +--- + +## Sources + +- [Calling Rust from the Frontend | Tauri v2](https://v2.tauri.app/develop/calling-rust/) +- [Why does Tauri modify the parameter names of invoked functions? — Stack Overflow](https://stackoverflow.com/questions/78432685/why-does-tauri-modify-the-parameter-names-of-invoked-functions) +- [Tauri Command Fundamentals — Tauri Tutorials](https://tauritutorials.com/blog/tauri-command-fundamentals) diff --git a/docs/05-代码审查/定时走查-2026-06-15-第6轮.md b/docs/05-代码审查/定时走查-2026-06-15-第6轮.md new file mode 100644 index 0000000..e924135 --- /dev/null +++ b/docs/05-代码审查/定时走查-2026-06-15-第6轮.md @@ -0,0 +1,73 @@ +# 定时代码走查 — 第 6 轮(2026-06-15) + +> 触发:定时走查 cron(每 30 分钟)。范围 = **列表页 + Dashboard + AI 面板/窗口垂直切片**(此前从未深查)。1 新提交(CR-22 修复)。 +> 方法:2 路代理(A 列表页 / B Dashboard+AI窗口)+ 主代理地面核 stopListener。 +> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 见 `docs/todo.md`。 +> 关联:[第1-5轮](./)。 + +--- + +## CR-22 ✅ + CR-23 撤销(价值第 5 次闭环) + +- `16aeacc` CR-22 ✅:types.ts 3 枚举注释对齐后端(Task/Idea/Project status)+ 删 AiConversationSummary.models 冗余 +- CR-23 撤销(不适用):接收方核对 B-34「Tauri 不转」注释在 ARC-05 拆分时已简化(workflow.ts:81/project.ts:58 仅风格对齐无错误说明) + +--- + +## 🔴 分离窗口生命周期(新功能首次深查,P1 核心) + +**代理 B R1-R5**。地面核 `stopListener`(useAiEvents.ts:258) **全仓零调用**(grep App/AiChat/AiDetached onUnmounted/onBeforeUnmount 都不调)确认 R2。 + +### CR-260615-24 [P1] listener 双注册 + 永不清理(R1+R2) +- **R1 双窗口重复消费 ai-chat-event**:后端 `app.emit`(stream_recv/agentic/commands/audit 多处)是**全局广播**发给所有 webview。主窗口(AiChat.vue:790)+ 分离窗口(AiDetached 渲染 AiChat)各注册 `listen('ai-chat-event')`,两窗口 handleEvent 都触发 → delta 各 `+=` 一次 / AiAgentRound 各 push 空气泡。当前靠 App.vue:94 `v-if="panelOpen && !detached"` detach 时卸载主窗口 AiChat 间接停,但 state 是模块级单例(handleEvent 仍写主窗口 state),**脆弱隐式契约**。 +- **R2 stopListener 零调用**:useAiEvents.ts:258 定义但全仓无调用点 → listener + watchdog 永久泄漏(startListener 幂等防重复注册,但卸载不释放)。 +- **边界影响**:reattach 重叠窗口期多一条空 assistant 消息(非崩溃/非数据损坏)。 +- **修**:AiChat.vue onBeforeUnmount 补 `store.stopListener()` + detach 时主窗口显式 stopListener / reattach startListener,或后端 `emit_to(label)` 定向。 + +### CR-260615-25 [P2] 分离窗口状态机缺口(R3/R4/R5) +- **R3**:`win.once('tauri://destroyed')` 置 detached=false 但**不恢复 panelOpen=true**(只 reattachPanel 显式设)→ 用户直接 X 关分离窗口后面板消失,需手动 Ctrl+I +- **R4**:`resumeInDetached`(useAiWindow.ts:79-95) 强行重建生成态(读 df-ai-gen 置 streaming=true + push 空气泡),与后端 `ai_is_generating` 真值可能失配 → 假气泡 / 130s 后 watchdog 假超时 +- **R5**:分离窗口 onMounted `switchConversation`(useAiWindow.ts:81) + loadConversations 内自动 switch,后端 **AiSession 单例** activeConversationId 被分离窗口覆盖 → 主窗口期间发消息可能落错会话(B-22 同类延伸) + +--- + +## 🟡 列表页(代理 A) + +### CR-260615-26 [P2] AR-11 + B-29 契约交互 + Tasks 空态(R-1/Y-2) +- **R-1**:AR-11 监听器(project.ts:38)对 task 调 `loadTasks()` **无参 → 全量加载**,破坏 B-260615-29 建立的「store.tasks 反映当前筛选」契约。filteredGroups 二次过滤**兜住结果正确**,但 store.tasks 变全量(createTask push 到全量数组)+ 性能 +- **Y-2**:Tasks.vue:39-71 **无 loading/error/empty 兜底**,加载失败或无结果时空白页(store.error/loading 零引用)。对比 Projects.vue:107 至少有空态 + +### ✅ 列表页亮点 +- **Projects/Tasks i18n 100%**(grep 确认中文仅注释/console)— CR-08 范围内这两页**无硬编码遗漏** +- Projects.vue useConfirm composable 抽离(DRY,消除 4 视图重复) +- B-260615-29 筛选重载注释保留(可追溯) + +--- + +## 🟡 Dashboard(代理 B) + +### CR-260615-27 [P2] refresh 兜底 + stage 颜色 + 死代码(M1/M3/L4) +- **M1**:refresh(Dashboard.vue:202-204)Promise.all **无 try/catch**,任一接口失败 unhandledrejection + 用户无反馈(onMounted 有 try/catch 但 refresh 无) +- **M3**:getProjectStage(:150) status→stage 映射 **planning 归 coding stage(颜色紫)但 label planning**,颜色语义撒谎(cancelled 归 testing 同) +- **L4**:决策/批注两 panel(:103-120) 永远空态,CSS(.decision-row/.anno-row :504-572) 完整但模板未用 = **死代码** + +### ✅ Dashboard 亮点 +- **无图表库**,纯 CSS 进度条 + 数字 stat,GPU 友好动画,**无性能问题** +- aiShared 破环正确(ARC-06),nextMsgId 两窗口各自独立不冲突 + +--- + +## 📊 本轮摘要 + +| 类别 | 数 | 代表 | +|---|---|---| +| 价值闭环 | 1 | CR-22 ✅ + CR-23 撤销 | +| P1 新发现 | 1 | 分离窗口 R1+R2 listener 双注册+泄漏(CR-24)| +| P2 新发现 | 3 | 分离窗口状态机(CR-25)/ 列表页契约+空态(CR-26)/ Dashboard(CR-27)| +| 亮点 | 4 | 列表页 i18n 100% / Dashboard 纯 CSS 无性能问题 / aiShared 破环 / useConfirm DRY | + +**本轮价值**:**分离窗口是新功能,前几轮横切(按提交/改动)完全没覆盖**,垂直切片(Dashboard+AI窗口维度)首次深查即暴露 P1(listener 泄漏)+ 状态机缺口。**再次印证 task2/3 垂直走查价值** — 横切收敛后,垂直切片是新发现来源(i18n/api/列表页/AI窗口 4 个垂直切片均挖出真问题)。 + +**方法论**:分离窗口 P1(R1+R2)经地面核 stopListener 零调用坐实(避免第5轮代理 A 假阳性教训),走查发现须地面验证。 + +todo 映射:CR-260615-24(P1 分离窗口 listener)/ CR-25(P2 状态机)/ CR-26(P2 列表页)/ CR-27(P2 Dashboard)。 diff --git a/docs/05-代码审查/定时走查-2026-06-15-第7轮.md b/docs/05-代码审查/定时走查-2026-06-15-第7轮.md new file mode 100644 index 0000000..bd249c7 --- /dev/null +++ b/docs/05-代码审查/定时走查-2026-06-15-第7轮.md @@ -0,0 +1,81 @@ +# 定时代码走查 — 第 7 轮(2026-06-15) + +> 触发:定时走查 cron(每 30 分钟)。范围 = **stores 独立 store 垂直切片**(ai/knowledge/appSettings/index,未审)+ 核 CR-24/26/27 修复。 +> 方法:主代理 git show 3 提交 + 1 路代理走查 stores。 +> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 见 `docs/todo.md`。 +> 关联:[第1-6轮](./)。 + +--- + +## CR-24/26/27 ✅(价值第 6 次闭环) + +| 提交 | CR | 修复 | +|---|---|---| +| ef2d033 | CR-24 ✅ | AiChat onBeforeUnmount 补 `store.stopListener()` + useAiEvents stopListener 补 `clearStreamWatchdog()`(卸载清 130s watchdog 避免写已卸载 state)| +| 7637f29 | CR-26 ✅ | project.ts `_activeTaskProject` 三态(undefined 不触发/all 全量/具体 id)+ Tasks.vue 空态兜底(89 行)| +| 5e6aa79 | CR-27 ✅ | Dashboard 删决策/批注死代码(-96 行)+ stage 颜色 + refresh try/catch | + +**CR-25(分离窗口状态机 R3/R4/R5)仍存**(无提交提及,接收方未修或在做)。 + +--- + +## 🔴 knowledge error 通道断(P1,CR-08 深化) + +### CR-260615-28 [P1] state.error 死字段 + create 语义矛盾 +- **state.error 死字段**:knowledge.ts:19,49,53,64,74,105 写入 `state.error`,但 **grep 全应用零消费**(Knowledge.vue store.error 零命中)→ 用户遭遇加载/检索/配置失败只看空白列表,无错误提示 +- **CR-08 深化**:CR-08「store error fallback 11 处中文」只记了硬编码,**整个 error 通道前端是断的**(写了从不读)— 比中文硬编码更严重 +- **create 语义矛盾**:create(:80-85) 成功后调 loadCandidates,若 loadCandidates 失败把错误塞 state.error,但 create 已返回 record 给调用方(认为成功) + +**修**:Knowledge.vue 接 error banner/toast,或删 state.error 改 throw 由调用方 catch + create 内 loadCandidates 失败不污染 error。 + +--- + +## ✅ ai.ts 状态机完整(亮点,验证修复质量) + +四核心态 `streaming/generatingConvId/currentText/queue` 在 **6 复位点闭环**,无卡死 streaming=true 缺口: +- AiCompleted(useAiEvents:189-217)/ AiError(:220-235) + queue=[](B-32) +- onStreamTimeout(useAiStream:25-28) + queue=[] + running→rejected(B-33) +- sendMessage catch(useAiSend:99-105)/ approveToolCall catch(:121-133) + queue=[] +- stopChat(:149-154) 本地先复位再发停止信号(不依赖后端 AiCompleted,防审批态看门狗已 clear 致 AiCompleted 竞态丢失) +- 分离窗口 resumeInDetached(useAiWindow:111) 先 invoke('ai_is_generating') 核对(R4 防 df-ai-gen 残留假气泡) + +> 验证:B-32/33/ef2d033 修复质量扎实,状态机无非法组合残留。 + +## ✅ appSettings 双源同步(亮点) + +三态 `cache(reactive)↔SQLite↔ref`: +- set 乐观写 cache + debounce 300ms 合并 + pendingValues 取最新值(非定时器快照),落库失败仅日志(UI 一致性优先,不回滚致不一致) +- useSetting 双 watch `Object.is` 守卫防循环 +- switchConversation 双 token 竞态防护(useAiConversations:44,48,52,122 `_latestSwitchId`) + +--- + +## 🟡 P2/P3 新发现 + +### CR-260615-29 [P2] ai.ts messages 无上限 + knowledge 竞态/错误不一致 +- **ai.ts messages 单对话无上限**:useAiSend push user+ai / useAiEvents push err/审批 / useAiStream push delta,单对话线性增长(切换对话整体替换是减负点,但同对话不轮换),长会话+大工具结果内存可观 +- **knowledge 异步无竞态**:loadList/search/loadConfig 无请求序号(messages 有 switch token),快速连点后返回覆盖先 +- **knowledge 错误处理不一致**:load*/search 有 catch 兜底,create/updateStatus/archive/saveConfig/extractNow 直抛(调用方无 catch 致 unhandled) + +### CR-260615-30 [P3] appSettings useSetting 对象值不落库(边界) +useSetting 双 watch `Object.is` 守卫对值类型完美防循环,但**对象/数组值深 mutate**(`r.value.x=1`)时 `Object.is(v,cache[key])` 同引用恒 true → 不触发 set 落库,静默不持久化。当前无 bug(useSetting 多绑值类型,df-ai-ui 走 set 整对象替换),隐式契约。 + +--- + +## 📊 本轮摘要 + +| 类别 | 数 | 代表 | +|---|---|---| +| 价值闭环 | 3 | CR-24/26/27 全修(修复质量好)| +| P1 新发现 | 1 | knowledge error 通道断(CR-28,CR-08 深化)| +| P2/P3 新发现 | 2 | ai.ts messages 无上限 + knowledge 竞态(CR-29)/ useSetting 对象值边界(CR-30)| +| 亮点 | 2 | ai.ts 状态机完整(6 复位点,验证修复质量)/ appSettings 双源同步设计扎实 | + +**本轮价值**: +1. **knowledge error 通道断**是新维度发现 — CR-08 只记中文硬编码,实际整个 error 通道前端断的(写了从不读),用户看不到知识库错误 +2. **ai.ts 状态机完整肯定前几轮修复质量**(B-32/33/ef2d033 闭环无卡死缺口)— 走查不仅找问题也验证修复 +3. CR-24/26/27 三项 P1/P2 全修,走查↔接收方闭环高效(第 6 次闭环) + +**方法论**:第 5 个垂直切片(stores),knowledge error 通道断是新发现维度(超越 i18n 的功能缺陷)。ai.ts 状态机完整是「走查验证修复」价值(非只找 bug)。 + +todo 映射:CR-260615-28(P1 knowledge error)/ CR-29(P2 messages+竞态)/ CR-30(P3 useSetting 边界)。 diff --git a/docs/05-代码审查/定时走查-2026-06-15-第8轮.md b/docs/05-代码审查/定时走查-2026-06-15-第8轮.md new file mode 100644 index 0000000..9c06f68 --- /dev/null +++ b/docs/05-代码审查/定时走查-2026-06-15-第8轮.md @@ -0,0 +1,64 @@ +# 定时代码走查 — 第 8 轮(前端收尾) + +> 触发:定时走查 cron(每 30 分钟)。范围 = **剩余小区域收尾**(useConfirm/useMarkdown/ConfirmDialog/ToolCardList/constants/utils)+ 核 CR-25/28/30 修复。 +> 方法:主代理 git show 3 提交 + 1 路代理收尾走查。 +> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only)。todo 见 `docs/todo.md`。 +> 关联:[第1-7轮](./)。 + +--- + +## CR-25/28/30 ✅(价值第 7 次闭环) + +| 提交 | CR | 修复 | +|---|---|---| +| ac8dab3 | CR-25 ✅ | 分离窗口状态机:destroyed 恢复 panelOpen(R3)+ resumeInDetached 核 ai_is_generating(R4)+ 删 switchConversation 自动 switch(R5)| +| 40461b2 | CR-28 ✅ | knowledge create 内 loadCandidates 改非阻塞(不污染成功语义)+ Knowledge.vue error banner 消费 store.error(**通道接通**)| +| b300bb7 | CR-30 ✅ | useSetting 值类型契约文档 + changed helper JSON 深比较 | + +**CR-29(ai.ts messages 无上限 + knowledge 竞态)仍存**。 + +## 数据完整性修复 + +todo:227 CR-30 行末**被污染**(上轮 Edit 锚错位接 CR-23 的 B-34 注释残留内容),本轮修复(删除 CR-23 残留段)。走查记录数据完整性已恢复。 + +--- + +## 收尾小区域(代理)— 无必修,质量过关 + +### CR-260615-31 [P2] 小区域健壮性(Y-1/Y-2) +- **Y-1 useConfirm 并发 confirmDialog 覆盖 resolve**(useConfirm.ts:35-47):连点两删除/confirm 交错时,第二个覆盖 `resolve`,前一个 Promise **永挂**(静默中断 + 轻微内存泄漏)。修:进入时 `resolve?.(false)` 打断前一个视为取消 +- **Y-2 ToolCardList collapseInactive 漏清 expandedTools**(:47-51):只清 expandedCards 未动 expandedTools,已完成卡片折叠后内容展开态残留 Set。修:对称过滤或注释明确有意保留 + +### ✅ 5 亮点(收尾区域是项目较干净部分) +- **G-1 useConfirm 抽取消除 5 处重复**(Projects/ProjectDetail/Ideas/Settings/AiChat)— DRY 到位 +- **G-2 useMarkdown 单例 + AiChat 流式 getter 复用** + **escapeHtml 全局单份** + **useRendered 已抽**(CR-10 无残留重复) +- **G-3 XSS 防护链完整**:renderMd `_purify.sanitize(_marked.parse(...))` + escapeFallback 纯文本转义双保险(B-24 覆盖) +- **G-4 project 常量统一映射**(label/badge/stage 三套 + accessor 兜底 + parseStack 防崩)— 根治三套矛盾 +- **G-5 ConfirmDialog 自包含**(样式内联不依赖外部 .btn-*,CSS 变量主题化) + +> 硬编码中文文案(AiChat:471/ProjectDetail:392)均带「i18n 不在白名单」注释,属已知取舍,CR-08 范围内不重复记。 + +--- + +## 📊 8 轮 cron 走查总结(前端全覆盖) + +| 维度 | 成果 | +|---|---| +| 轮次 | 8 轮 cron + 5 垂直切片(i18n/api/列表页/AI窗口/stores)+ 收尾 | +| 价值闭环 | **7 次**(B-32/33/34/35 + AR-11 + CR-18/19/21/22/24/25/26/27/28/30 全被接收方修复)| +| 假阳性纠正 | 1 次(Tauri camelCase,代理 A 6 处误判经权威裁决纠正)| +| 数据完整性 | 1 次污染修复(CR-30 行末 CR-23 残留)| +| 方法论 | 走查须地面 Read/权威为准(非注释);多代理交叉验证有效;垂直切片是横切收敛后的新发现来源 | + +**前端剩余未修**:CR-29(ai.ts messages 无上限 + knowledge 竞态)/ CR-31(useConfirm/ToolCardList 健壮性)/ CR-08(i18n 硬编码 40+)/ CR-09/10(DRY .ai-md/renderedRef)/ CR-12(可选一组)。 + +--- + +## 💡 建议 + +前端走查已**充分覆盖**(8 轮 + 5 垂直切片,收尾无必修,接收方修复速度跟上发现速度)。建议: + +1. **停止 cron**(前端边际收益递减)— 用户可 `/loop` 停止或让我 CronDelete +2. **转 task2/3 后端 workflow 功能/模块垂直走查**(新维度:后端 src-tauri + 8 crate 全链路,此前前端走查未覆盖后端) + +todo 映射:CR-260615-31(P2 收尾健壮性)。 diff --git a/docs/05-代码审查/工作区多角度走查-2026-06-15.md b/docs/05-代码审查/工作区多角度走查-2026-06-15.md index a5bf72b..12e4550 100644 --- a/docs/05-代码审查/工作区多角度走查-2026-06-15.md +++ b/docs/05-代码审查/工作区多角度走查-2026-06-15.md @@ -177,7 +177,7 @@ | # | 等级 | 文件:行 | 修改内容 | 对应 todo | |---|------|---------|----------|-----------| -| ① | 🔴P0 | project.ts:273 | selectType→select_type,多选审批静默失效 | B-260615-31 | +| ① | 🔴P0 | project.ts:273 | selectType→select_type,多选审批静默失效 | B-260615-34 | | ② | 🔴P0 | useAiSend/Stream/Events | timeout/error/stop 收尾清队列,防消息丢失 | B-260615-32 | | ③ | 🔴P0 | useAiStream onStreamTimeout | 回滚 running toolCall,防骨架屏卡死(B-07 残留) | B-260615-33 | | ④ | 🔴P1 | ProjectDetail:201 + useAiStream:39 + AiChat:675 | i18n 一组(多选按钮🔴+错误文案🟡+toast⚪) | CR-260615-08 | diff --git a/docs/05-代码审查/文档全量核对报告-2026-06-15.md b/docs/05-代码审查/文档全量核对报告-2026-06-15.md new file mode 100644 index 0000000..20f71c9 --- /dev/null +++ b/docs/05-代码审查/文档全量核对报告-2026-06-15.md @@ -0,0 +1,141 @@ +# 文档全量核对报告(2026-06-15) + +> 方法:4 路并行代理核对(A 根目录核心 / B 架构+模块 vs 代码 / C 审查+迭代+管理 / D 交叉引用+规范+重复) +> 范围:docs/ 全量 48 .md + 根目录 ARCHITECTURE.md / PROGRESS.md / URGENT.md +> 性质:核对记录,不改代码(本会话 session-role)。todo 待修项见 docs/todo.md「文档核对待修项」DOC-260615-01~14。 +> **悬空链接:0**(全量 markdown 链接核对,目标文件全部存在)。 + +--- + +## P0 文档硬伤(按文档找代码会落空) + +### 1. ARCHITECTURE.md 数据模型整段漂移(B-1 + A) +:262-475 列 14 表,**6 虚构 7 遗漏**:`artifacts`/`connections`/`features`/`test_cases`/`test_runs`/`annotations`/`decisions` 7 张不存在(df-traceability 删后未清);`workflow_defs`/`workflow_runs` 应合并为 `workflow_executions`;实际 `node_executions`/`branches`/`ai_conversations`/`knowledges`/`ai_tool_executions`/`knowledge_events`/`app_settings` 7 张未列。projects 表字段(repo_path/priority/current_stage)多数从未加。**建议**:整段重写对齐 migrations.rs,或加「⚠️ 初稿,实际 schema 见 migrations.rs」横幅。 + +### 2. df-nodes 模块文档 8 节点全骨架 → 实际 3 节点 2 已实现(A-1/C-1/D-3.2) +:9,13,17-26 列 8 节点(AI/Script/Docker/Git/Human/Notify/HTTP/Subflow)全标骨架 execute() 空。实际 `crates/df-nodes/src/` 仅 ai_node/script_node/human_node 3 文件,**均完整实现**(script 接 shell、ai 调 provider、human subscribe→send→select!)。Docker/Git/Notify/HTTP/Subflow 5 节点不存在。**建议**:重写,节点缩 3 个状态改 ✅。 + +### 3. df-ai 模块文档列不存在的文件 + 已删 trait(A-3) +:30-40 文件结构列 `router.rs`(ModelRouter)+ `stream.rs`(StreamCollector),实际均无(R-PD-7 删)。:50-72 LlmProvider trait 含 `supported_features()`(已删,新增 `endpoint()`)。:22/174 工具数 12 → 实际 13。**建议**:删两虚构文件行,trait 段对齐,12→13。 + +### 4. 使用手册 7 处与实际不符(C-4.4) +运行命令 `bun`→实际 `npm`;任务状态 4 态→实际 7 态(todo/in_progress/in_review/testing/done/blocked/cancelled);优先级 P0-P3 方向歧义(前后端枚举反向);节点类型 Script/Condition/Parallel→实际 Script/Ai/Human(无独立 Condition/Parallel,且 Script 已下线);监控「WebSocket」→实际 Tauri EventBus;「单 Provider」→实际多 Provider;知识库「静态收集」→实际 Tier1 AI 提炼+向量检索。**建议**:全面重写。 + +--- + +## P1 审查状态断层(报告说修了/todo 看不到) + +### 5. 全局 review R-P1-1~6 / R-P2-1~13 已实施 todo 零回写(C-1.2/1.3) +全局代码review-2026-06-15.md 推进状态标「P1×6 + P2×13 全闭环,126 test pass」,代码核对坐实 R-P1-2/3/5/6、R-P2-2/3/8/9/10/13 均落地。**todo.md 完全无条目**(既不在待办也不在已完成)。**建议**:已完成区补汇总回写。 + +### 6. 架构复核报告 §5 新发现 11 项零入 todo(C-1.1) +架构与缺陷复核报告 §5 新①~⑪(5 项🟠),todo 零覆盖。复核-新②/⑨/⑩ 后被全局 review 取代,**复核-新①/③/④/⑪ 待补**:①useAiConversations pendingToolCalls 二次 token 比对 / ③executor NodeCancelled 事件语义双标 / ④cancel_workflow_node 终态守卫 / ⑪主窗口幽灵 watchdog。**建议**:4 项补 todo。 + +### 7. aichat §8 + 工作流审批 TL;DR 已修项未勾(B-3/B-4) +aichat审查报告 §8 优先级表 11 行仅 AR-3 标 ✅,实际 H1/H2/create_project/AR-5/6/7/9/10 均 ✅(todo 对齐)。工作流审批审查报告 TL;DR + §0 头号 P0(human_node.rs:41 缺 await)B-03b-R6 ✅ 已修但报告仍「待修」语气。**建议**:补 ✅ + commit 标注。 + +### 8. 近期改动 CR-03 未做子项埋 [x] 备注(C-1.4) +CR-260615-03 标 [x] 但备注含「未做:audit.rs Err 误报/openai source 丢失/ToolCard projectNameById find」4 子项,代码核对坐实未改。**建议**:拆独立 P2 todo(或合并 CR-260615-11)。 + +--- + +## P1 架构/模块文档过期 + +### 9. df-workflow 4 处接口过期(B-A-2) +:81 NodeRegistry Default(已删,registry.rs:76 注「不实现」);:180 ConditionEngine 默认 true→实际 false(B-260614-02);:138 try_recv_human_approval(已删);:155-156 set_waiting/set_skipped(已删,仅留 set_cancelled)。**建议**:4 处对齐。 + +### 10. df-storage V8→V13(B-A-4/C-4.1) +:3,9,26-36,114,132 反复 V1-V8/MIGRATION_VERSION=8,实际 V1-V13(V9 ai_providers/V10 knowledges.reasoning+knowledge_events/V11 projects.deleted_at/V12 path+stack/V13 app_settings)。表数 11→18,Repo 11→17。**建议**:版本/表数/Repo 数全改。 + +### 11. 对抗评估 idea_evaluations 设计稿未标未实施(B-A-5) +想法探索-对抗式评估 :220-310 设计 6 struct + idea_evaluations 表,全仓零命中。实际 adversarial.rs 纯启发式(AdversarialEval/Argument/AnalystAnalysis),功能决策记录已标「📐 设计未实施」但模块文档未标。**建议**:加「⚠️ 当前纯启发式,三路 LLM + 表为设计稿」横幅。 + +### 12. df-execute 文档 Shell/Docker/SSH/Git vs 仅 Shell(D-3.3) +ARCHITECTURE:84/PROGRESS:29/df-nodes 文档描述 df-execute Shell/Docker/SSH/Git,实际仅 shell.rs+lib.rs(docker/git_ops/ssh Sprint 19 已删)。**建议**:删 Docker/SSH/Git 描述。 + +--- + +## P1 索引/计数矛盾 + +### 13. 13→8 crate 多处(A/D-3.1) +实际 workspace 8 crate。PROGRESS:13/INDEX:4,107,119/Phase1决策 ADR-003/对抗裁决报告/想法探索 多处写 13。**建议**:统一「原 13,2026-06-14 删 5 零引用,现 8」(ADR 加「已修正」标注保留决策史)。 + +### 14. INDEX 漏 9 文档 + 09-问题排查目录(A/D-2.3)— **本会话已补正** +F-07 trait下沉/密钥迁移/工作流边界/条件引擎/generating状态机(02 区 5)/DAG引擎详解/AI对话引擎(03 区 2)/全局代码review(05 区 1)/aichat-apikey-401排查(09 目录)。**已在 INDEX 补**。 + +### 15. commands 数 47/57/68 三处冲突(C-5.1) +PROGRESS:72 写 47、:48 写 57、实际 68 个 #[tauri::command]。**建议**:统一 68。 + +### 16. URGENT.md 过时 + 未挂索引(D-5.1/2.4) +5 个 P0(AC3/FR-S7/FR-S8/B-03b-R8/FR-S1)在 todo 全 ✅,URGENT 无停用标注 + 不在任何索引。**建议**:顶部加「⚠️ 2026-06-14 快照,P0 全迁 todo 已完成,本文件停用」或删除。 + +--- + +## P2 迭代/Phase 过期 + +### 17. DEVFLOW-1~4 全标待实施(C-2.1) +4 文档全「状态:待实施/实施记录待填写」,实际 Phase1 全完成(DEVFLOW-1 宏替代 trait 设计转向/DEVFLOW-2 68 commands/3-4 落地)。**建议**:标已完成+实施摘要,或归档。 + +### 18. Phase1 任务清单严重滞后(C-3.1) +标「进行中」,实际全完成。待实施表 #7~#11 全 [ ](均已做)。代码规模 71 文件 4108 行 vs 实际 53/7400。已知问题 4 项 3 项已解决。**建议**:标完成或归档。 + +### 19. Phase2 计划部分超前/滞后(C-3.2) +Phase2.2/2.3 基础已完成(启发式)未回写;Phase2.4 并行 ✅/编辑器+条件分支未做;B 路线 conditions 默认值已改未同步。**建议**:标注实际状态。 + +--- + +## P2 三层规范 + +### 20. todo 肥大违反分层(D-2.1) +todo:88-101/251-267/293-296 块是 mini 审查报告塞看板(单条 10-30 行),应压成指针进 docs。**建议**:每条压到「ID — [P级] 一句话 — 详见<文档>」。 + +### 21. 重复收口(D-4.1-4.3) +HumanNode 审批三处(功能决策记录未瘦身为指针,B-03 文档标真相源但功能决策记录仍全文重复);密钥迁移两处(全局review 应仅留指针);FR-S1 四处(todo 与 URGENT 既重复又矛盾)。**建议**:真相源收口,余改指针。 + +### 22. 文档记录规范未在 INDEX 突出(D-5.3) +文档记录规范-2026-06-14.md 是三层规范权威定义,INDEX 仅普通条目,README 未提。**建议**:INDEX/README 加「规范基准」标注。 + +--- + +## P3 技术/工具文档 + +### 23. Tauri-IPC 全篇过时(C-4.2) +:8 「仅 greet 示例」→实际 68 commands;:16-23 待实施 5 项全落地;:35 `frontend/src/`→实际 `src/`。**建议**:重写或归档。 + +### 24. View 改造指南全篇过时(C-4.3) +:7-13 当前问题 4 项全解决;:80-86 页面 ProjectsView 等(实际无 View 后缀,无 WorkflowView)。**建议**:归档(与 DEVFLOW-3 重复)。 + +### 25. SQLite 表数 V1-V9→V1-V13(C-4.1,与 §10 同源) + +### 26. 锚点顿号 + 路径风格(A/D) +INDEX:107-109 `#四crate-结构` 锚点顿号渲染器差异(低危);功能创意池:150 链接省 `./` 风格不一。 + +--- + +## ✅ 一致性良好 + +- **df-knowledge 模块文档**:与代码吻合 +- **规格契约自检机制**:活契约机制描述准确 +- **功能决策记录**:状态标注详尽且与 todo 对齐(决策反转有留痕) +- **经验记录**:落地项均标 ✅ + commit +- **todo.md**:项目内最准真相源,审查报告交叉对照基本可靠 +- **悬空链接 0**:全量 markdown 链接目标全部存在 + +--- + +## 📊 摘要 + +| 类别 | 数 | 代表 | +|---|---|---| +| P0 文档硬伤 | 4 | ARCHITECTURE 数据模型 / df-nodes / df-ai / 使用手册 | +| P1 审查断层 | 4 | R-P1/P2 未回写 / 复核新①③④⑪ / §8 表 / CR-03 子项 | +| P1 模块过期 | 4 | df-workflow / df-storage / 对抗评估 / df-execute | +| P1 索引计数 | 4 | 13→8 / INDEX漏 / commands冲突 / URGENT | +| P2 迭代规范 | 6 | DEVFLOW / Phase1 / Phase2 / todo肥大 / 重复 / 规范未突出 | +| P3 技术文档 | 4 | Tauri-IPC / View指南 / SQLite / 锚点 | + +**最优先**:P0 四项(ARCHITECTURE 数据模型重写 / df-nodes 重写 / df-ai 删虚构 / 使用手册重写)—— 按文档找代码会落空,误导接手方最强。 + +**已本会话补正**:INDEX 漏列 9 文档 + 09 目录(DOC-14 部分);ID 碰撞 B-260615-31→34。 + +todo 映射:DOC-260615-01~14(见 docs/todo.md「文档核对待修项」)。 diff --git a/docs/INDEX.md b/docs/INDEX.md index d14260c..c0a9710 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -34,14 +34,21 @@ docs/ │ ├── aichat信息密度构想-2026-06-14.md │ ├── 任务推进构想-2026-06-14.md │ ├── aichat流式Markdown渲染调研-2026-06-15.md # 流式渲染优化方案(rAF节流/块级diff/换库) -│ └── 工作流审批审查报告-2026-06-14.md +│ ├── 工作流审批审查报告-2026-06-14.md +│ ├── F-07-df-ai-core-trait下沉设计-2026-06-14.md +│ ├── 密钥迁移健壮性-2026-06-15.md +│ ├── 工作流脚本执行边界-2026-06-15.md +│ ├── 条件表达式引擎-2026-06-15.md +│ └── generating状态机加固-2026-06-15.md ├── 03-模块文档/ │ ├── df-storage-存储层-2026-06-12.md │ ├── df-workflow-工作流引擎-2026-06-12.md │ ├── df-nodes-节点集合-2026-06-12.md │ ├── df-ai-AI集成模块-2026-06-12.md │ ├── df-knowledge-知识库-2026-06-14.md -│ └── 想法探索-对抗式评估-2026-06-12.md +│ ├── 想法探索-对抗式评估-2026-06-12.md +│ ├── DAG引擎详解-2026-06-14.md +│ └── AI对话引擎-2026-06-14.md ├── 04-功能迭代/ │ ├── DEVFLOW-1.CRUD层实施-2026-06-12.md │ ├── DEVFLOW-2.IPC桥接实施-2026-06-12.md @@ -53,14 +60,26 @@ docs/ │ ├── 近期改动代码审查-2026-06-15.md # 工作区 FR-S1/S7/S8 + 近 5 提交(3 路并行) │ ├── 架构审查-2026-06-15.md # 纯架构层(边界/依赖/抽象/扩展性),8 crate + 前端(2 路并行) │ ├── 自研块级memo流式渲染审查-2026-06-15.md # ARC-260615-08 实施走查(splitBlocks/parseBlock/rAF) -│ └── 工作区多角度走查-2026-06-15.md # 工作区22文件547行4路并行(selectType/队列收尾/骨架屏/i18n/DRY) +│ ├── 工作区多角度走查-2026-06-15.md # 工作区22文件547行4路并行(selectType/队列收尾/骨架屏/i18n/DRY) +│ ├── 定时走查-2026-06-15-P0复核.md # 定时走查第1轮:B-34已修(B-32·33结论后被第2轮纠正为已修)/B-35 broadcast Lagged新P0/aiShared破环亮点 +│ ├── 定时走查-2026-06-15-第2轮.md # 定时走查第2轮:B-32·33·34全修确认(第3次纠正过时)/AR-11前端listener永不attach新P0/ARC-05 store拆分优/CR-11健壮性✅ +│ ├── 定时走查-2026-06-15-第3轮.md # 定时走查第3轮:走查价值闭环(AR-11/B-35/CR-18·19全修确认+CR-20撤销误判)/前端P0全闭环/CR-21 App.vue try/catch轻量 +│ ├── 定时走查-2026-06-15-第4轮.md # 定时走查第4轮:i18n垂直切片(key树466全对齐优秀)/硬编码40+处(CR-08扩展)/P1-1 useAiEvents:182 i18n破坏bug改一行最高ROI +│ ├── 定时走查-2026-06-15-第5轮.md # 定时走查第5轮:api垂直切片/Tauri v2默认转camelCase权威裁决(代理A 6处IPC遗漏假阳性纠正)/B-34错误注释传播误导(CR-23)/types对齐良好 +│ ├── 定时走查-2026-06-15-第6轮.md # 定时走查第6轮:列表页+Dashboard+AI窗口垂直切片/分离窗口生命周期P1(listener双注册+永不清理CR-24)/状态机缺口(CR-25)/列表页契约(CR-26)/Dashboard(CR-27) +│ ├── 定时走查-2026-06-15-第7轮.md # 定时走查第7轮:stores垂直切片/CR-24·26·27三修确认(价值第6闭环)/knowledge error通道断P1(CR-28,CR-08深化)/ai.ts状态机完整亮点(验证修复质量) +│ ├── 定时走查-2026-06-15-第8轮.md # 定时走查第8轮(前端收尾):CR-25·28·30三修确认(价值第7闭环)/CR-30污染修复/收尾小区域无必修(CR-31 useConfirm/ToolCardList)+5亮点(XSS防护链/useConfirm DRY)/8轮总结+停止cron建议 +│ ├── 全局代码review-2026-06-15.md # 7维度并行(DRY/架构/bug/安全/AI可靠/工作流),P1×6+P2×13全闭环 +│ └── 文档全量核对报告-2026-06-15.md # docs全量+根目录4路核对(ARCHITECTURE漂移/模块文档过期/审查状态断层) ├── 06-前端开发/ │ └── View改造指南-2026-06-12.md ├── 07-项目管理/ │ ├── Phase1任务清单-2026-06-12.md │ └── Phase2计划-2026-06-12.md -└── 08-用户指南/ - └── 使用手册-2026-06-12.md +├── 08-用户指南/ +│ └── 使用手册-2026-06-12.md +└── 09-问题排查/ + └── aichat-apikey-401排查-2026-06-15.md ``` --- @@ -80,6 +99,7 @@ docs/ | 🎨 前端开发 | [06-前端开发/](./06-前端开发/) | Vue 3 前端分析、优化、迁移指南 | | 📊 项目管理 | [07-项目管理/](./07-项目管理/) | 任务清单、开发计划、进度跟踪 | | 📚 用户指南 | [08-用户指南/](./08-用户指南/) | 快速上手、配置指南、FAQ | +| 🐛 问题排查 | [09-问题排查/](./09-问题排查/) | 排查记录、根因分析 | --- diff --git a/docs/ai-file-ops-manual.md b/docs/ai-file-ops-manual.md new file mode 100644 index 0000000..d10653f --- /dev/null +++ b/docs/ai-file-ops-manual.md @@ -0,0 +1,189 @@ +# AI 文件操作工具能力手册 + +> 最后更新:2026-06-15 | 基于全量测试验证 + +--- + +## 工具总览 + +| 工具 | 用途 | 可靠性 | +|------|------|--------| +| `read_file` | 读取文件内容(全文/分页) | ✅ 高 | +| `write_file` | 创建/覆盖写入文件(纯文本) | ✅ 高 | +| `list_directory` | 列出目录内容(含递归/深度控制) | ✅ 高 | +| `run_command` | 执行 shell 命令 | ⚠️ 可执行,但 stdout 不可用 | + +--- + +## read_file + +读取文件内容,支持分页。 + +### 参数 + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `path` | string | ✅ | 文件路径,支持 `/` 和 `\` | +| `offset` | number | ❌ | 起始行(0 基索引),默认 0 | +| `limit` | number | ❌ | 读取行数,默认全部 | + +### 行为规则 + +- `offset` 是 **0 基索引**:offset=0 → 第 1 行,offset=10 → 第 11 行 +- `offset` 超出总行数 → 返回空内容,**不报错** +- `limit` 超出剩余行数 → 返回到末尾,**不报错** +- 空文件 → 返回 `{ content: "", lines: 0, size: 0 }` +- 不存在的文件 → 报错 `os error 2` +- 二进制文件 → 返回 `{ binary: true, content: null, error: "文件非 UTF-8 文本" }` +- 路径越界(项目目录外)→ 被拦截 + +### 返回结构 + +```json +{ + "content": "文件文本内容", + "lines": 30, // 总行数(始终是全量值,非当前页行数) + "size": 145, // 总字节数(始终是全量值) + "path": "..." +} +``` + +> ⚠️ `lines` 和 `size` 始终返回**全量值**,不受 offset/limit 影响。 + +--- + +## write_file + +创建或覆盖写入文件,纯文本模式。 + +### 参数 + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `path` | string | ✅ | 文件路径 | +| `content` | string | ✅ | 文本内容(UTF-8) | + +### 行为规则 + +- **自动创建多级父目录**:路径中不存在的目录会自动创建 +- **覆盖已有文件**:直接覆盖,无确认机制(⚠️ 注意安全) +- 返回 `old_size`(覆盖前大小,新建时为 null)和 `bytes_written`(写入大小) +- 支持 emoji、引号、反斜杠、中文等特殊字符 +- 路径分隔符 `/` 和 `\` 均可 +- 中文路径/文件名含空格 → 正常工作 +- 空内容(`content=""`)→ 创建 0 字节文件 +- **无法写入二进制数据**(NULL 字节等会被当文本处理) +- 路径越界(项目目录外)→ 被拦截 + +### 局部修改的正确做法 + +write_file 是全量覆盖,局部修改需三步: + +``` +1. read_file 获取完整内容 +2. AI 在内存中替换目标行/文本 +3. write_file 写回完整内容 +``` + +--- + +## list_directory + +列出目录内容,支持递归和深度控制。 + +### 参数 + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `path` | string | ✅ | 目录路径 | +| `recursive` | boolean | ❌ | 是否递归,默认 false | +| `max_depth` | number | ❌ | 递归最大深度(配合 recursive) | +| `skip_noise_dirs` | boolean | ❌ | 跳过 node_modules/.git 等,默认 false | + +### 返回结构 + +```json +{ + "entries": [ + { "name": "src", "type": "directory", "size": 0, "depth": 0 }, + { "name": "main.ts", "type": "file", "size": 466, "depth": 0 } + ], + "truncated": false +} +``` + +### 行为规则 + +- `type` 取值:`"directory"` | `"file"` +- `depth`:0 = 根层级,1 = 一级子目录,以此类推 +- 不存在的目录 → 报错 `os error 3` +- `truncated: false` 表示结果完整未截断 + +--- + +## run_command + +执行 shell 命令。 + +### 参数 + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `command` | string | ✅ | shell 命令(非交互式) | +| `working_dir` | string | ❌ | 工作目录 | +| `timeout_secs` | number | ❌ | 超时秒数,默认 60 | + +### ⚠️ 已知问题 + +**stdout 始终返回空字符串**(已确认是普遍问题): +- `echo`、`Write-Output` 等输出无法被捕获 +- 命令本身**能正常执行**(cp/mv/rm/重定向写文件均验证通过) +- stderr 大部分场景也为空 + +### 可靠使用的场景 + +| 场景 | 可靠性 | 示例 | +|------|--------|------| +| 删除文件 `rm` | ✅ | `rm -f path/to/file` | +| 复制文件 `cp` | ✅ | `cp src.txt dst.txt` | +| 移动/重命名 `mv` | ✅ | `mv old.txt new.txt` | +| 创建目录 `mkdir` | ✅ | `mkdir -p deep/nested/dir` | +| 写入文件(重定向) | ✅ | `echo content > file.txt` | +| 二进制写入 `printf` | ✅ | `printf '\x89PNG' > file.png` | +| 超时控制 | ✅ | `timeout_secs=3` 正确中断 | +| 工作目录切换 | ✅ | `working_dir` 生效,相对路径可用 | +| 获取命令输出 | ❌ | stdout/stderr 不可用 | + +--- + +## 快速参考:常见操作怎么做 + +| 我想要... | 工具调用 | +|-----------|---------| +| 读文件全文 | `read_file(path)` | +| 读第 10~15 行 | `read_file(path, offset=9, limit=6)` | +| 创建新文件 | `write_file(path, content)` | +| 修改文件中某几行 | `read_file` → AI 替换 → `write_file` | +| 追加内容到文件末尾 | `read_file` → 拼接 → `write_file` | +| 删除文件 | `run_command("rm -f path")` | +| 重命名文件 | `run_command("mv old new")` | +| 列出项目结构 | `list_directory(path, recursive=true, max_depth=3)` | +| 搜索文件内容 | 分页 `read_file` 逐段扫描(无原生搜索) | +| 搜索文件名 | `list_directory` 递归 + AI 过滤(无原生 glob) | +| 写入二进制文件 | `run_command("printf '\x89...' > file")` | +| 获取文件大小/行数 | `read_file`(必须读内容,无轻量元信息工具) | + +--- + +## 待实现能力(已建任务) + +| 能力 | 任务 ID | 优先级 | +|------|---------|--------| +| 文件内容搜索 `search_in_file` | F-FILE-01 | P1 | +| 文件局部更新 `patch_file` | F-FILE-02 | P1 | +| 文件元信息 `file_info` | F-FILE-03 | P1 | +| run_command stdout 修复 | F-FILE-07 | P1 | +| 追加写入 `append_file` | F-FILE-04 | P2 | +| 文件名搜索 `search_files` | F-FILE-05 | P2 | +| Base64 二进制写入 | F-FILE-06 | P2 | +| 文件差异对比 `diff_file` | F-FILE-08 | P3 | diff --git a/docs/todo.md b/docs/todo.md index ba7e043..b3b471a 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -74,7 +74,8 @@ - [ ] AR-8 delta 节流+滚动 — **重评(2026-06-15)**:前端 rAF 节流已被 ARC-08 覆盖(每帧 ≤1 parse);剩后端 50ms 合批(B-260615-02 心跳已动 stream_recv.rs,合批可并入同文件)+ 滚动跟随。降优先级 — stream_recv.rs + AiChat.vue 滚动 - [x] AR-9 ~~friendlyError 硬编码中文~~ ✅ Wave3 完成(friendlyError 全走 i18n.global.t + zh/en 双语补 4 key;TS2589 用 as any 规避 vue-i18n 深度泛型)(commit 9e2aeff) - [x] AR-10 ~~想法→灵感迁移残留~~ ✅ 已统一(13 文件批量:i18n zh-CN + 后端错误 + LLM 描述/提示词 + store toast;en 待定 Ideas/Idea、docs 注释低优先略)(commit 65c475b) -- [ ] AR-11 数据变更联动刷新(推荐方案A 后端 emit + store 监听)— 跨层 — 详见 [审查第五章](./02-架构设计/aichat审查报告-2026-06-14.md) — **勘察完成(2026-06-15,wxflofhf2)**:feasible/risk 中/跨 8 文件(audit.rs/commands.rs/tool_registry.rs/stores/project.ts/useAiEvents.ts/Projects/Tasks/ProjectDetail.vue)。方案A 方向合理(emit df-data-changed+entity/action 分类+store listen)但勘察 implPlan 含伪代码错误(std::env::var/.match Rust 不存在=agent 幻觉)+碰 7 近期活跃文件含未提交 P0 改动的 commands.rs。**暂缓**:等本批提交后主代理重设计 emit 点(audit.rs 签名是否有 app_handle 待核查)再派 +- [x] AR-11 ✅(定时走查第3轮,2026-06-15,dc27e79) — ~~数据变更联动刷新~~(推荐方案A 后端 emit + store 监听)— 跨层 — 详见 [审查第五章](./02-架构设计/aichat审查报告-2026-06-14.md) — **勘察完成(2026-06-15,wxflofhf2)**:feasible/risk 中/跨 8 文件(audit.rs/commands.rs/tool_registry.rs/stores/project.ts/useAiEvents.ts/Projects/Tasks/ProjectDetail.vue)。方案A 方向合理(emit df-data-changed+entity/action 分类+store listen)但勘察 implPlan 含伪代码错误(std::env::var/.match Rust 不存在=agent 幻觉)+碰 7 近期活跃文件含未提交 P0 改动的 commands.rs。**暂缓(原)**:等本批提交后主代理重设计 emit 点 — **第2轮定时走查复核(2026-06-15):后端已实施 ✅,前端半接通 🔴** — 后端 `emit_data_changed`(audit.rs:259-270)+ 自动执行路径(audit.rs:351)+ 审批路径(commands.rs:175)三处 emit 就位且逻辑正确;前端工作区(project.ts:31-55,109-110)定义 `startDataChangedListener`/`stopDataChangedListener` + export,**但 App.vue onMounted 未挂载 → listener 永不 attach → 数据变更后列表不刷新(AR-11 功能失效,后端 emit 成死事件)**。修:App.vue onMounted 调 `startDataChangedListener()` + onUnmounted stop(对齐 workflow listener 生命周期)。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md) + > **第3轮(2026-06-15):listener 已 attach ✅** — dc27e79 `App.vue:218` onMounted `await startDataChangedListener()` + :258 onUnmounted stop。AR-11 功能闭环(后端 emit df-data-changed + 前端 listen 按 entity 调 load)。详见 [定时走查-2026-06-15-第3轮.md](./05-代码审查/定时走查-2026-06-15-第3轮.md) ### 🔴 aichat API Key 401 排查(2026-06-15) @@ -196,18 +197,65 @@ > 4 路并行代理走查工作区 22 文件 547 行(AiChat/View/stores/composables+ToolCard),详见 [工作区多角度走查-2026-06-15.md](./05-代码审查/工作区多角度走查-2026-06-15.md)。3 P0 功能 bug + 1 P1 i18n + DRY/健壮性一组。 **P0 — 功能 bug** -- [ ] B-260615-31 — **[P0]** selectType 字段名与后端 IPC 不匹配 → 多选审批静默失效。`project.ts:273` 传 selectType(camelCase),后端 `workflow.rs:211` 签名 select_type(snake_case),Tauri 2 默认不转换 → 后端收 None 归一化 Single → 多选提交被拒「单选只能一个决策」。单选偶然兼容掩盖。铁证:同 invoke 的 execution_id/node_id 已 snake_case 唯独此项破坏 + grep selectType src-tauri/ 零命中。修:`selectType:`→`select_type:` + 删错误注释 — stores/project.ts:273 — 详见走查 ① -- [ ] B-260615-32 — **[P0]** 流式 timeout/error/stop 收尾不清队列 → 队列消息静默丢失。drainQueue 仅 AiCompleted 触发(useAiEvents:218),onStreamTimeout/AiError/stopChat 三路径不清 state.queue → 生成中输入的消息丢失无提示。approveToolCall catch 同漏。修:三路径 + approve catch 补 `state.queue=[]` + 提示。关联 B-260615-22(状态不同步,不同角度) — useAiSend.ts:36-42 + useAiStream.ts onStreamTimeout + useAiEvents.ts AiError case + useAiSend.ts:82-107 — 详见走查 ② -- [ ] B-260615-33 — **[P0]** onStreamTimeout 不回滚 running toolCall → 审批后卡片永久骨架屏。approveToolCall 乐观置 running,后端 hang 时看门狗触发 onStreamTimeout 只复位 streaming 不碰 toolCalls[].status → running 态只渲染骨架(ToolCard:24) 审批按钮不显 → 无重审入口。**B-260615-07 残留**(B-07 加了 watchdog 但回调没回滚 status)。修:onStreamTimeout 扫一遍 running toolCall→rejected — useAiStream.ts:21-49 — 详见走查 ③ +- [x] B-260615-34 ✅(定时走查复核,2026-06-15) — ~~selectType snake_case 对齐~~ `project.ts:273` 已改 `select_type: ... ?? 'single'` + 正确注释「Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211」;后端 `workflow.rs:211` 同 `select_type: Option`,前后端契约对齐。**纠正上轮过时判断**(上轮压缩前快照仍 camelCase,之后 `80b9243`「P0 bug 修复」批已修;后端代理经 IPC 签名推断「前端已对齐」结论正确) — stores/project.ts:273 — 详见定时走查 P0复核.md +- [x] B-260615-32 ✅(定时走查第2轮复核,2026-06-15,f93b758) — ~~流式收尾清队列~~ **[原 P0]**drainQueue 仅 AiCompleted 触发(useAiEvents:218),onStreamTimeout/AiError/stopChat 三路径不清 state.queue → 生成中输入的消息丢失无提示。approveToolCall catch 同漏。修:三路径 + approve catch 补 `state.queue=[]` + 提示。关联 B-260615-22(状态不同步,不同角度) — useAiSend.ts:36-42 + useAiStream.ts onStreamTimeout + useAiEvents.ts AiError case + useAiSend.ts:82-107 — 详见走查 ② + > ⚠️ **第2轮复核更正(2026-06-15):已修 ✅** — f93b758「修复: B-32+33 流式收尾」:`useAiStream.ts:28` `state.queue=[]`(onStreamTimeout) + `useAiEvents.ts:225`(AiError) + `useAiSend.ts:152`(stopChat)/124(approveToolCall catch)四路径全清队列。上轮「仍未修」为过时快照(working tree 当时旧版,与 B-34 同)。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md) +- [x] B-260615-33 ✅(定时走查第2轮复核,2026-06-15,f93b758) — ~~回滚 running toolCall~~ **[原 P0]**approveToolCall 乐观置 running,后端 hang 时看门狗触发 onStreamTimeout 只复位 streaming 不碰 toolCalls[].status → running 态只渲染骨架(ToolCard:24) 审批按钮不显 → 无重审入口。**B-260615-07 残留**(B-07 加了 watchdog 但回调没回滚 status)。修:onStreamTimeout 扫一遍 running toolCall→rejected — useAiStream.ts:21-49 — 详见走查 ③ + > ⚠️ **第2轮复核更正(2026-06-15):已修 ✅** — f93b758:`useAiStream.ts:43-45` onStreamTimeout 单遍反向扫描 running toolCall→rejected(合并探测 completed 为单次 O(n))。上轮「仍未修」为过时快照。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md) +- [x] B-260615-35 ✅(定时走查第3轮复核,2026-06-15,b08adcb) — **[原 P0]** broadcast `Lagged` 兜底仅 warn → 关键终态事件丢失时前端永久卡死。`workflow.rs:90-110` forward 任务靠 `finished = matches!(event, WorkflowCompleted|WorkflowFailed)` 退出,broadcast 滑动窗口 Lagged(n) 丢 n 条最旧事件,**代码注释已识别风险但仅 warn 无兜底**;若 Lagged 恰丢终态事件,forward 永远 `rx.recv().await` 等不到 finished → 前端审批/完成弹窗永久卡死,仅靠 DB 轮询兜底(实时性差)。修:Lagged 分支后累计次数/单次 n 过大时查 `WorkflowRepo::get_by_id` 看终态,补 emit workflow-event + break;需把 `state.db.clone()` move 进 forward 任务(当前仅 executor spawn 持有)。低频但无自愈 — src-tauri/src/commands/workflow.rs:90-110 — 详见定时走查 P0复核.md(后端代理 ②) **P1 — i18n** -- [ ] CR-260615-08 — **[P1]** i18n 硬编码一组:ProjectDetail:201 多选按钮「确认(N)」🔴 + useAiStream:39-41 onStreamTimeout 两错误文案🟡 + AiChat:675 handleSend toast⚪。英文 locale 中英混杂。修:补 `projectDetail.approvalConfirm/Count` + `ai.streamInterrupted(AfterTool)` + `aiChat.toastSendFail` 中英 key 改 `t()`。confirmClearChat 硬编码接 B-260615-20 已记技术债一并清 — ProjectDetail/useAiStream/AiChat + i18n — 详见走查 ④ +- [ ] CR-260615-08 — **[P1]** i18n 硬编码一组(定时走查第4轮扩展:原记 3 处,实际 **40+ 处**;i18n key 树本身优秀 466 key zh/en 全对齐 0 缺,问题全在 src/ 硬编码)。**P1 直接破坏 i18n(最高优先)**:①useAiEvents:182 `tc.result='用户拒绝了此操作'` 绕过已有 aiTool.rejectedHint → en locale 拒绝提示恒中文(已有 en 翻译被一行废掉),**改一行删/换 t() 最高 ROI**;②TaskDetail.vue 整页 13 处未接 i18n(无 taskDetail namespace,需新建);③ProjectDetail 审批/导入 5 处(导入目录/确认N/取消/confirmDialog/Message);④Dashboard:76 暂无项目;⑤AiChat:107/471/687(title清空/confirmDialog/showToast)。**P2**:⑥ToolCard:152-164 ARG_LABEL_MAP 11 审批参数标签硬编码(aiTool 无 key,需新增 argLabels.*);⑦store error fallback 11 处全中文(knowledge/ideas/projects/tasks/workflow);⑧useAiSend:49/65/130(队列满/审批未送达);⑨useAiStream:52-53 看门狗两文案(原 CR-08 行号 39-41 偏差)。**P3**:⑩time.ts:46-51 相对时间中文(已有 common.justNow/minutesAgo 等 key 却没用,en locale 时间恒中文,隐蔽);⑪router meta.title 8 处(0 消费方死代码);⑫fallbackLocale:'en' 设计气味。修:按视图分批接入,**P1-1 改一行先做** — 全 src/ + i18n — 详见定时走查第4轮.md **P1/P2 — DRY + 健壮性** -- [ ] CR-260615-09 — **[P1]** 四份 .ai-md 样式逐字重复 ~180 行(ProjectDetail/Ideas/Knowledge/TaskDetail) → 抽全局 `src/styles/ai-md.css` 或 `` 组件。B-24/25 复制粘贴源头 — 详见走查 ⑤ +- [ ] CR-260615-09 — **[P1]** 五份 .ai-md 样式逐字重复 ~350 行(AiChat/ProjectDetail/Ideas/Knowledge/TaskDetail) → 抽全局 `src/styles/ai-md.css` 或 `` 组件。TaskDetail.vue:263-333 新文件继续抄(CR-09 复发,定时走查 06-15 确认第 5 份)。B-24/25 复制粘贴源头 — 详见走查 ⑤ + 定时走查 P0复核.md - [ ] CR-260615-10 — **[P2]** useMarkdown 加 `useRendered(getText)` 辅助,消除 renderedDesc computed + onMounted(loadMarkdown) 三处重复(与 CR-09 同源) — 详见走查 ⑥ -- [ ] CR-260615-11 — **[P2]** composables+ToolCard 健壮性一组:⑦switchConversation JSON.parse 无逐条容错(单条坏 args 清空整对话 useAiConversations:81) ⑧args 消费 `as any` 类型逃逸(ToolCard:241,328) ⑨approveToolCall 重复查找应复用 findToolCall(useAiSend:82) ⑩useAiEvents switch 缺 AiHeartbeat case(:118) ⑪approveHumanApproval 签名 decision/decisions 歧义(project.ts:255) ⑫ToolCard formatBytes/命名判定/key 兜底 — 详见走查 ⑦~⑫ +- [x] CR-260615-11 ✅(定时走查第2轮复核,2026-06-15,fddca9d) — ~~composables+ToolCard 健壮性一组~~ **[原 P2]** ⑦⑧⑨⑩⑪⑫ fddca9d 已实施(JSON.parse 逐条容错/argString 去 as any/复用 findToolCall/AiHeartbeat 显式 case/approveHumanApproval 签名收敛/formatToolName 兜底)。原问题:⑦switchConversation JSON.parse 无逐条容错(useAiConversations:81) ⑧args 消费 `as any` 类型逃逸(ToolCard) ⑨approveToolCall 重复查找(findToolCall) ⑩useAiEvents switch 缺 AiHeartbeat case ⑪approveHumanApproval 签名歧义 ⑫ToolCard 兜底。**⚠️⑪ 实施引入死逻辑**:workflow.ts:67-68 decision 三元两分支相同(见 CR-260615-18)— 详见走查 ⑦~⑫ + 定时走查第2轮.md - [ ] CR-260615-12 — **[P3]** 可选一组:_toastTimer 卸载未清(AiChat:436) + markdown 外层 span→div + SVG 常量散落 + appSettings 注释 + index 空行 + 路由 icon 语义 + options shape 校验 + projectNameById 共享 + useConfirm dev 警告 + i18n `t` as any — 详见走查 ⚪ 区 +- [x] CR-260615-18 ✅(定时走查第3轮,2026-06-15,f574562) — **[原 P2]** decision 三元两分支相同死逻辑(CR-11⑪ 实施引入):`workflow.ts:67-68` `selectType === 'multiple' ? decisions[0] ?? '' : decisions[0] ?? ''` 两分支返回值完全一致,三元判断无意义,误导维护者以为单/多选差异化。后端 workflow.rs:291-296 兜底(空 picked+非空 decision push)。修:直接 `const decision = decisions[0] ?? ''` 删三元 — src/stores/project/workflow.ts:67-68 — 详见定时走查第2轮.md +- [x] CR-260615-19 ✅(定时走查第3轮,2026-06-15,f574562) — **[原 P3]** action 字段(保留为未来差异化刷新预留,注释已明确)定义 emit 但前端不消费:DfDataChangedPayload.action(api/types.ts:157) 后端 emit_data_changed(audit.rs:243-256) 每次计算,前端 listener(project.ts:33) 只解构 entity,action 忽略,刷新统一全量 load。契约冗余(白算白传)。修:删 action 或做差异化刷新(delete 本地移除)— src/api/types.ts:157 + project.ts:33 — 详见定时走查第2轮.md +- [x] CR-260615-20 ✅撤销(定时走查第3轮,2026-06-15) — 走查误判:接收方核 workflow.ts:108 stopEventListener 写法一致(f574562 commit 说明),不修 — stopDataChangedListener try/catch 过度防御:project.ts:47-55 unlisten 同步数组操作正常不抛,与 workflow.ts:108 stopEventListener 写法不一致。修:核 workflow.ts:108 对齐统一去/留 — src/stores/project.ts:47-55 — 详见定时走查第2轮.md +- [x] CR-260615-21 ✅(定时走查第4轮,2026-06-15,94cdcdc) — **[原 P3]** App.vue:218 `startDataChangedListener()` await 包 try/catch console.error(防 Tauri listen 失败 reject onMounted 致 AR-11 静默失效)。原问题:await 无 try/catch — src/App.vue:218 — 详见定时走查第3轮.md +- [x] CR-260615-22 ✅(定时走查第6轮,2026-06-15,16aeacc) — **[原 P3]** types.ts 枚举注释对齐(Task/Idea/Project status)+ 删 AiConversationSummary.models 冗余 枚举注释与后端不符(api 垂直切片,定时走查第5轮代理 B):①TaskRecord.status(types.ts:88)注释 review_ready/merged/abandoned 错,后端 TaskStatus(types.rs:165)= todo/in_progress/in_review/testing/done/blocked/cancelled;②IdeaRecord.status(:11)漏 archived;③ProjectRecord.status(:39)漏 testing/releasing。status 字段是 string 非 literal union 不阻断,但注释误导。④AiConversationSummary.models(:233)冗余(后端返但前端无消费点)。修:注释对齐后端枚举/删冗余字段 — src/api/types.ts — 详见定时走查第5轮.md +- [x] CR-260615-23 ✅撤销(定时走查第6轮,2026-06-15,不适用) — B-34「Tauri 不转」注释在 ARC-05 拆分时已简化(workflow.ts:81/project.ts:58 仅风格对齐无错误说明,16aeacc 接收方核对) — project.ts:273 +- [x] CR-260615-24 ✅(定时走查第7轮,2026-06-15,ef2d033) — **[原 P1]** 分离窗口 listener 双注册+永不清理(Dashboard+AI窗口垂直切片,定时走查第6轮代理 B R1+R2,新功能首次深查):①R1 双窗口重复消费 ai-chat-event:后端 app.emit 全局广播(stream_recv/agentic/commands/audit 多处)发给所有 webview,主窗口(AiChat.vue:790)+分离窗口(AiDetached 渲染 AiChat)各注册 listen,两窗口 handleEvent 都触发 → delta 各 += 一次/AiAgentRound 各 push 空气泡,靠 App.vue:94 v-if detach 时卸载主窗口 AiChat 间接停但 state 模块级单例 handleEvent 仍写(脆弱隐式契约);②R2 stopListener(useAiEvents.ts:258)定义但**全仓零调用**(地面 grep App/AiChat/AiDetached onUnmounted/onBeforeUnmount 都不调)→ listener+watchdog 永久泄漏。边界影响:reattach 重叠窗口期多一条空 assistant 消息。修:AiChat.vue onBeforeUnmount 补 store.stopListener()+detach 主窗口显式 stop/reattach start,或后端 emit_to(label) 定向 — src/composables/ai/useAiEvents.ts:258 + useAiWindow.ts + components/AiChat.vue:747 — 详见定时走查第6轮.md +- [x] CR-260615-25 ✅(定时走查第8轮,2026-06-15,ac8dab3) — **[原 P2]** 分离窗口状态机缺口(代理 B R3/R4/R5):③R3 win.once('tauri://destroyed')置 detached=false 但不恢复 panelOpen=true(只 reattachPanel 显式设)→ X 关分离窗口后面板消失需手动 Ctrl+I;④R4 resumeInDetached(useAiWindow.ts:79-95)强行重建生成态(读 df-ai-gen 置 streaming=true push 空气泡)与后端 ai_is_generating 真值可能失配 → 假气泡/130s 后 watchdog 假超时;⑤R5 分离窗口 switchConversation(useAiWindow.ts:81)+loadConversations 内自动 switch,后端 AiSession 单例 activeConversationId 被覆盖,主窗口期间发消息可能落错会话(B-22 同类延伸)。修:destroyed 补 panelOpen=true/resumeInDetached 先 await ai_is_generating 核对/分离窗口只读不切 — src/composables/ai/useAiWindow.ts — 详见定时走查第6轮.md +- [x] CR-260615-26 ✅(定时走查第7轮,2026-06-15,7637f29) — **[原 P2]** AR-11 listener 与 B-29 筛选契约交互 + Tasks 空态(列表页垂直切片,代理 A R-1/Y-2):①R-1 AR-11 监听器(project.ts:38)对 task 调 loadTasks() 无参 → 全量加载,破坏 B-260615-29「store.tasks 反映当前筛选」契约(filteredGroups 二次过滤兜住结果正确,但 store.tasks 变全量+性能);②Y-2 Tasks.vue:39-71 无 loading/error/empty 兜底,加载失败或无结果时空白页(store.error/loading 零引用,对比 Projects.vue:107 至少有空态)。修:listener 对 task 按 activeProject 筛选拉/Tasks 加 v-if loading/error/empty — src/stores/project.ts:38 + src/views/Tasks.vue — 详见定时走查第6轮.md +- [x] CR-260615-27 ✅(定时走查第7轮,2026-06-15,5e6aa79) — **[原 P2]** Dashboard 一组(代理 B M1/M3/L4):①M1 refresh(Dashboard.vue:202-204)Promise.all 无 try/catch,任一接口失败 unhandledrejection+用户无反馈(onMounted 有 try/catch 但 refresh 无);②M3 getProjectStage(:150)status→stage 映射 planning 归 coding stage(颜色紫)但 label planning,颜色语义撒谎(cancelled 归 testing 同);③L4 决策/批注两 panel(:103-120)永远空态,CSS(:504-572)完整但模板未用=死代码。修:refresh 包 try/catch+toast/补 planning CSS 类/删死代码或接数据 — src/views/Dashboard.vue — 详见定时走查第6轮.md +- [x] CR-260615-28 ✅(定时走查第8轮,2026-06-15,40461b2) — **[原 P1]** knowledge store state.error 死字段(error 通道前端断)+create 语义矛盾(stores 垂直切片,定时走查第7轮):①state.error(knowledge.ts:19,49,53,64,74,105)写入但**全应用零消费**(grep Knowledge.vue store.error 零命中)→ 用户遭遇加载/检索/配置失败只看到空白列表无错误提示,**CR-08「store error fallback 11 处中文」深化**:不仅中文硬编码,整个 error 通道前端断的;②create(:80-85)成功后 loadCandidates 失败把错误塞 state.error,create 已返回 record 给调用方认为成功,语义自相矛盾。修:Knowledge.vue 接 error banner/toast 或删 state.error 改 throw 由调用方 catch + create 内 loadCandidates 失败不污染 error — src/stores/knowledge.ts — 详见定时走查第7轮.md +- [x] CR-260615-29 ✅(定时走查第9轮,2026-06-15,6116c9b) — **[原 P2]** ai.ts messages 单对话无上限 + knowledge 异步竞态/错误不一致(定时走查第7轮):①ai.ts state.messages(useAiSend push user+ai/useAiEvents push err 审批/useAiStream push delta)单对话内线性增长,长会话+大工具结果内存可观(切换对话整体替换是减负点,同对话不轮换),建议软上限/滚动淘汰;②knowledge.ts 加载/检索/配置异步无竞态保护(messages 有 switch token,items/config 无),快速连点后返回覆盖先;③knowledge 错误处理不一致(load*/search 有 catch 兜底,create/updateStatus/archive/saveConfig/extractNow 直抛)。修:messages 软上限/items+config 加请求序号/错误处理统一 — src/stores/ai.ts + knowledge.ts — 详见定时走查第7轮.md +- [x] CR-260615-30 ✅(定时走查第8轮,2026-06-15,b300bb7) — **[原 P3]** appSettings useSetting 对象/数组值深 mutate 不落库(定时走查第7轮边界):useSetting 双 watch 用 Object.is 守卫防循环,但对对象/数组值深 mutate(r.value.x=1)时 Object.is(v,cache[key]) 同引用恒 true → 不触发 set 落库,静默不持久化。当前无 bug(useSetting 多绑值类型,df-ai-ui 走 set 整对象替换非 mutate),但隐式契约。修:文档注明 useSetting 仅值类型,或对象值用 JSON 序列化比较 — src/stores/appSettings.ts:88-113 — 详见定时走查第7轮.md +- [x] CR-260615-31 ✅(定时走查第9轮,2026-06-15,2ddd294) — **[原 P2]** 小区域收尾健壮性一组(定时走查第8轮,无必修):①useConfirm 并发 confirmDialog 覆盖 resolve(useConfirm.ts:35-47),前一个 Promise 永挂(连点两删除/confirm 交错时)→ 静默中断+轻微内存泄漏,修:进入时 resolve?.(false) 打断前一个视为取消;②ToolCardList collapseInactive(:47-51)只清 expandedCards 漏清 expandedTools,已完成卡片折叠后内容展开态残留 Set,修:对称过滤或注释明确有意保留 — src/composables/useConfirm.ts + components/ToolCardList.vue — 详见定时走查第8轮.md + +### 📄 文档核对待修项(2026-06-15) + +> 4 路并行核对 docs 全量 + 根目录文档,详见 [文档全量核对报告-2026-06-15.md](./05-代码审查/文档全量核对报告-2026-06-15.md)。悬空链接 0。 + +**P0 — 文档硬伤(按文档找代码落空)** +- [ ] DOC-260615-01 — **[P0]** ARCHITECTURE.md 数据模型整段重写(14 表 6 虚构 artifacts/connections/features/test_cases/test_runs/annotations/decisions + 7 遗漏 node_executions/branches/ai_conversations 等;workflow_defs/runs 合并 workflow_executions)— ARCHITECTURE.md:262-475 — 详见核对报告 §1 +- [ ] DOC-260615-02 — **[P0]** df-nodes 模块文档重写(8 节点全骨架 → 实际 3 节点 ai/script/human 均完整实现;删 Docker/Git/Notify/HTTP/Subflow 5 虚构)— 03-模块文档/df-nodes — 详见 §2 +- [ ] DOC-260615-03 — **[P0]** df-ai 模块文档删虚构 router.rs/stream.rs + 已删 supported_features trait + 12→13 工具 — 03-模块文档/df-ai — 详见 §3 +- [ ] DOC-260615-04 — **[P0]** 使用手册全面重写 7 处(运行命令 bun→npm / 任务状态 4→7 态 / 优先级方向 / 节点类型 Script/Ai/Human / EventBus 非 WebSocket / 多 Provider / 知识库 Tier1)— 08-用户指南/使用手册 — 详见 §4 + +**P1 — 审查状态断层** +- [ ] DOC-260615-05 — todo 补全局 review R-P1-1~6/R-P2-1~13 已完成回写(报告说全闭环 126 test pass,todo 零条目)— 已完成区补汇总 — 详见 §5 +- [ ] DOC-260615-06 — 架构复核报告 §5 新发现 11 项补 todo(复核-新①③④⑪ 待补:pendingToolCalls 二次 token / NodeCancelled 事件语义 / cancel 终态守卫 / 主窗口幽灵 watchdog;余已被全局 review 取代)— 详见 §6 +- [ ] DOC-260615-07 — aichat §8 表 9 行 + 工作流审批 TL;DR 已修项补 ✅ 勾选(H1/H2/create_project/AR-5~10 + human_node.rs:41 await B-03b-R6)— 详见 §7 +- [ ] DOC-260615-08 — 近期改动 CR-03 未做子项拆独立 todo(audit.rs 三臂 / openai Error::from / ToolCard projectNameById find)— 详见 §8 + +**P1 — 架构/模块文档过期** +- [ ] DOC-260615-09 — df-workflow 模块文档 4 处过期(NodeRegistry 删 Default / conditions 默认 true→false / try_recv_human_approval 已删 / set_waiting·set_skipped 已删)— 详见 §9 +- [ ] DOC-260615-10 — df-storage V8→V13(迁移版本+表数 11→18+Repo 11→17)+ 对抗评估 idea_evaluations 设计稿标未实施 + df-execute 描述删 Docker/SSH/Git — 详见 §10-12 + +**P1 — 索引/计数矛盾** +- [ ] DOC-260615-11 — 13→8 crate 多处统一(PROGRESS/INDEX/Phase1决策/对抗裁决/想法探索)+ commands 数 47/57/68 统一 68 + URGENT.md 停用标注或删 — 详见 §13/15/16(INDEX 漏列部分本会话已补,见 DOC-14) + +**P2 — 迭代/Phase/规范** +- [ ] DOC-260615-12 — DEVFLOW-1~4 + Phase1/Phase2 标完成或归档(Phase1 已收尾文档仍全待实施)— 详见 §17-19 +- [ ] DOC-260615-13 — todo 肥大瘦身(大段详情移 docs 子目录)+ 重复收口(HumanNode 审批/密钥迁移/FR-S1)+ 文档记录规范在 INDEX 突出 — 详见 §20-22 + +**P3 — 技术/工具文档** +- [ ] DOC-260615-14 — Tauri-IPC/View 改造指南全篇过时重写或归档 + SQLite 表数 V1-V9→V1-V13 + 锚点顿号 — 详见 §23-26 ### 🟡 B-03b 复核后续(独立深度复核,2026-06-14) @@ -278,7 +326,18 @@ - [x] B-260615-21 ✅(P0批,2026-06-15,待commit) — ~~sendMessage catch 回滚 user message~~ push user msg 前捕获 const userMsgId(原内联未生成)+catch 块 filter 改 m.id!==aiMsgId && m.id!==userMsgId 一并回滚;机制:user/ai msg 均 nextMsgId() 唯一 id+push 进 state.messages(无索引依赖,按 id 过滤最稳) — useAiSend.ts **P2 — 前后端状态同步** -- [ ] B-260615-22 — [P2] 前后端状态不同步:前端 state.streaming 与后端 generating 各自维护,后端卡死/异常退出时前端不知情(streaming 可能已被 AiError 复位 false,预检 useAiSend.ts:36 放行撞后端 :45 拦截)。评估:发送前 IPC 查后端真实 generating,或卡死主动 emit 状态同步事件。 +- [ ] B-260615-22 — [P2] ~~前后端状态不同步~~ → **由 B-260616-01(L0 握手) + B-260616-02(L2 强制发送) 解决**,本条保留为问题记录不再独立实施 +- [ ] CR-260615-12 — **[P3]** 可选一组:_toastTimer 卸载未清(AiChat:436) + markdown 外层 span→div + SVG 常量散落 + appSettings 注释 + index 空行 + 路由 icon 语义 + options shape 校验 + projectNameById 共享 + useConfirm dev 警告 + i18n `t` as any — 详见走查 ⚪ 区 +- [ ] CR-260615-09 — **[P1]** 五份 .ai-md 样式逐字重复 ~350 行(AiChat/ProjectDetail/Ideas/Knowledge/TaskDetail) → 抽全局 `src/styles/ai-md.css` 或 `` 组件。TaskDetail.vue:263-333 新文件继续抄(CR-09 复发,定时走查 06-15 确认第 5 份)。B-24/25 复制粘贴源头 — 详见走查 ⑤ + 定时走查 P0复核.md +- [ ] CR-260615-10 — **[P2]** useMarkdown 加 `useRendered(getText)` 辅助,消除 renderedDesc computed + onMounted(loadMarkdown) 三处重复(与 CR-09 同源) — 详见走查 ⑥ +- [ ] CR-260615-08 — **[P1]** i18n 硬编码一组(定时走查第4轮扩展:原记 3 处,实际 **40+ 处**;i18n key 树本身优秀 466 key zh/en 全对齐 0 缺,问题全在 src/ 硬编码)。**P1 直接破坏 i18n(最高优先)**:①useAiEvents:182 `tc.result='用户拒绝了此操作'` 绕过已有 aiTool.rejectedHint → en locale 拒绝提示恒中文(已有 en 翻译被一行废掉),**改一行删/换 t() 最高 ROI**;②TaskDetail.vue 整页 13 处未接 i18n(无 taskDetail namespace,需新建);③ProjectDetail 审批/导入 5 处(导入目录/确认N/取消/confirmDialog/Message);④Dashboard:76 暂无项目;⑤AiChat:107/471/687(title清空/confirmDialog/showToast)。**P2**:⑥ToolCard:152-164 ARG_LABEL_MAP 11 审批参数标签硬编码(aiTool 无 key,需新增 argLabels.*);⑦store error fallback 11 处全中文(knowledge/ideas/projects/tasks/workflow);⑧useAiSend:49/65/130(队列满/审批未送达);⑨useAiStream:52-53 看门狗两文案(原 CR-08 行号 39-41 偏差)。**P3**:⑩time.ts:46-51 相对时间中文(已有 common.justNow/minutesAgo 等 key 却没用,en locale 时间恒中文,隐蔽);⑪router meta.title 8 处(0 消费方死代码);⑫fallbackLocale:'en' 设计气味。修:按视图分批接入,**P1-1 改一行先做** — 全 src/ + i18n — 详见定时走查第4轮.md + +### 🔴 前后端状态分裂根治(HMR/刷新/切窗口自愈) + +> 用户实测(06-15):DEV 运行中改代码触发 Vite HMR → 前端重载但后端 Tauri 进程未重启 → `generating=true` 残留 → 所有新消息被拦(`ai_chat_send` 同步检查)→ 入队死锁 10+ 分钟不恢复。**根因**:前后端独立生命周期无 reconciliation 协议。**否决超时方案**(5-10min 等死体验差)。选 L0 重接握手(零延迟自愈) + L2 发送韧性(用户可控)。 + +- [ ] B-260616-01 [P0] — **L0 前端重连握手(状态分裂根治)**。前端每次 startListener 完成后 emit `ai-client-ready` 事件(携带 client_id + last_conv_id);后端注册 listener 收到后:①`generating=true`? → 强制 reset + emit AiCompleted(补偿事件让前端收尾清 watchdog/续发队列)②`pending_approvals` 有残留? → 清除(审批态已无前端 UI 可操作)③记录 `last_heartbeat = now`。覆盖场景:Vite HMR / 页面 F5 / 分离窗口重建 / 切换标签页后台回收。**改动**:前端 `useAiEvents.ts:startListener()` 末尾加 emit(~3 行);后端 `mod.rs` 或新建 `commands/ai/handshake.rs` 注册 listener + 状态清洗(~30 行)。**用户体验**:HMR 后零延迟自动恢复,无需用户干预 — src/composables/ai/useAiEvents.ts + src-tauri/src/commands/ai/mod.rs +- [ ] B-260616-02 [P1] — **L2 发送时强制覆盖(用户可控自救)**。当前行为:`generating=true` → `ai_chat_send` 返回 Err → 前端 catch 静默入队 → 死锁无出口。改进三级降级:①正常(generating=false)→ 直接发送 ②排队(<30s, generating=true)→ 入队 + UI 显示「⚳ 排队中 N 条」+ 可取消按钮 ③异常(>30s 或 用户主动)→ 弹确认「检测到生成状态异常,是否强制发送?」→ 调用新 IPC `ai_chat_force_send` 绕过 generating 检查(后端先 reset generating 再正常走 send 流程)。**关键设计**:30s 后给用户选择权而非永远静默等。**改动**:前端 `useAiSend.ts` sendMessage 加超时分支 + confirm 弹窗;后端 `commands.rs` 新增 `ai_chat_force_send`(复用 ai_chat_send 逻辑,前置 reset generating)。关联 B-10(软复位已做 newConversation, force_send 同模式) — src/composables/ai/useAiSend.ts + src/components/AiChat.vue + src-tauri/src/commands/ai/commands.rs ### 🔴 详情描述字段 Markdown 未渲染(2026-06-15 用户报障) @@ -308,6 +367,80 @@ - [x] B-260615-31 ✅(2026-06-15,待commit) — ~~TaskDetail + ProjectDetail 字段同行布局~~ `.info-item` flex-direction column→row + align-items baseline + gap 12px + `.label` min-width 88px flex-shrink:0(TaskDetail `.value` 加 flex:1 min-width:0 占余);描述字段 info-item 加 `info-block` class + CSS `.info-item.info-block{flex-direction:column}` 保块状(长文本独占整行);ProjectDetail `.path-row`/`.info-tags` 已 flex-wrap 无溢出风险。vue-tsc 0 err — src/views/TaskDetail.vue + src/views/ProjectDetail.vue +### 🔴 run_command stdout/stderr 恒空(2026-06-15 用户实测) + +> 用户实测 F-05 run_command:命令能跑(副作用如写文件发生)+ exit_code 正常,但 **stdout 始终返回空字符串**,echo/Write-Output 等均如此 → "写→跑→**看**→改"闭环断在「看」,跑测试/构建无法获取输出。**根因(铁证)**:`crates/df-execute/src/shell.rs:37-58` 构造 `tokio::process::Command` 时**漏设 `cmd.stdout(Stdio::piped()).stderr(Stdio::piped())`**。tokio 默认 `Stdio::inherit()`(输出继承父进程 Tauri app → 控制台/devnull),`wait_with_output()` 读不到 pipe → `output.stdout/stderr` 空 Vec → `from_utf8_lossy` → 空串。F-05 plan doc 也未提 Stdio 设置,实现遗漏。**PS 静默失败(.Replace()/-replace/Set-Content exit_code=0 文件未变)疑非独立 bug**:修了 piped 后 stderr/stdout 可观测,方能定位是「PS Replace 源不匹配」(AI 用法)还是「cmd /C 调 PS 引号问题」(shell.rs:38 Windows 用 cmd /C,PS 语法引号嵌套易错)。**B-38 待 B-37 修复后据观测重评**。 + +- [ ] B-260615-37 [P0] — **shell.rs execute 补 Stdio::piped()**。`shell.rs:58` spawn 前 加 `cmd.stdout(Stdio::piped()).stderr(Stdio::piped())`(需 `use tokio::process::Stdio;`)。修后 stdout/stderr 正常捕获,run_command 闭环恢复。最小改动 1 行 + import — crates/df-execute/src/shell.rs:37-58 — source:用户实测(06-15) +- [ ] B-260615-38 [P1] — **PS 静默失败根因**(待 B-37 修后据 stderr 观测重评)。候选:a) PS Replace/-replace 源字符串不匹配(AI 用法,非 bug);b) shell.rs:38 Windows `cmd /C` 不适配 PS 语法(引号嵌套/管道),需支持 shell 选择(powershell -NoProfile -Command / cmd /C / sh -c)。B-37 修复前无法定位 — crates/df-execute/src/shell.rs:37-39 — source:用户实测(06-15) + +### 🔴 查看灵感来源跳空白页(2026-06-15 用户报障) + +> 用户报障:项目详情点「来源灵感」(i18n key `projectDetail.sourceIdea`) 跳转后页面空白。**根因双锁**:① `ProjectDetail.vue:71` `router-link :to="/ideas/${idea_id}"` 指向 `/ideas/:id`,但**路由表(router/index.ts:21-25)只有 `/ideas` 精确路由,无 `:id` 子路由** → hash 路由无匹配 → 渲染空白(路由表无 404 catch-all 兜底)。② 即便补路由,`Ideas.vue` 不读 route params——`:209` import 无 `useRoute` / `:221` 只 `useRouter` / `:448-450` onMounted 只 `store.loadIdeas()` 不接 params → `selectedId`(:230) 纯内部 ref,无法 deep link 选中灵感。**数据层 OK**:`Project.idea_id` 字段(models.rs:38)+晋升写入(idea.rs:124),ProjectDetail 拿得到 idea_id。**对照**:`/projects/:id` `/tasks/:id` 路由已建(独立详情页),Ideas 是双栏列表+详情同页故未建 `:id`,但 ProjectDetail 反向溯源需 deep link。 + +- [ ] B-260615-36 [P1] — **查看灵感来源跳空白页**。修复两层:① `router/index.ts` 加 `/ideas/:id` 路由(name `IdeasDetail` 或复用 Ideas 组件);② `Ideas.vue` 加 `useRoute` + `onMounted` 读 `route.params.id`→`selectedId.value=id`(loadIdeas 后校验 id 在 `store.ideas` 中,不在则提示「灵感不存在/已删」+清 selectedId);可选 watch `route.params.id` 支持从详情再点别的灵感切换。— src/router/index.ts:21-25 + src/views/Ideas.vue:209,221,448-450 — source:用户报障(06-15) + +### 🟡 AiChat 气泡拷贝能力(2026-06-15 用户需求) + +> 用户需求:AiChat 消息气泡支持一键拷贝——**文本拷贝**(content 原文→剪贴板) + **截图拷贝**(气泡 DOM 渲染→图片→剪贴板,粘贴出图片)。**现状**:全项目零 clipboard 实现(grep `navigator.clipboard`/`copy`/`clipboard`/`拷贝`/`复制` 零命中)。气泡结构:用户消息 `.ai-msg-bubble--user`(纯文本 `{{msg.content}}`, :185);AI 消息 `.ai-msg-bubble--ai.ai-md`(v-html 渲染 MD, :197)。**Tauri WebView2(Edge)**:Clipboard API 完整支持(`writeText`/`write([ClipboardItem])`),无需额外 plugin(做减法,不用 `@tauri-apps/plugin-clipboard-manager`)。**安全约束**:必须在用户点击事件(手势)内调用剪贴板 API。 + +- [ ] F-260615-13 [P2] — **AiChat 气泡文本拷贝**。每条消息气泡右上角加拷贝按钮(📋 icon,hover 显示)。点击 → `navigator.clipboard.writeText(msg.content)` → 短暂 toast「已复制」。AI 气泡拷原始 content(非 v-html 渲染后 HTML,防 HTML 标签污染);用户气泡同。按钮位置:`.ai-msg-bubble` 内右下角或 hover 时右上角浮现(不占空间,信息密度优先)。i18n 加 `aiChat.copied` / `aiChat.copyMsg`。— src/components/AiChat.vue(:185 用户/:197 AI) + src/i18n +- [ ] F-260615-14 [P2] — **AiChat 气泡截图拷贝(图片)**。同位置第二按钮(🖼️ 或与文本拷贝合并为下拉)。技术链:`html2canvas(bubbleElement)` → canvas → `canvas.toBlob('image/png')` → `new ClipboardItem({ 'image/png': blob })` → `navigator.clipboard.write([item])`。需加依赖 `html2canvas`(~90KB,或轻量替代 `dom-to-image-more` ~3KB,待选型)。**注意**:html2canvas 对 CSS 支持有限(阴影/渐变/CSS 变量可能不全),但气泡样式简单(ai-md 基本色+圆框),风险低。粘贴目标:Word/微信/钉钉等富文本编辑器可接图片;纯文本框不行(正常行为)。— src/components/AiChat.vue + 新依赖(html2canvas/dom-to-image-more 待选) + +### 🟡 AI Chat 交互体验改进(2025-07-15 方案 → 待办化) + +> 来源:[aichat交互体验改进方案-2025-07-15.md](./02-架构设计/aichat交互体验改进方案-2025-07-15.md)。7 大类 20 个改进项,按原文优先级分 4 批。**去重**:§1.3 复制 ⊂ F-260615-13/14(已写)、§3.4 新建中断 ⊂ B-260615-10(已做软复位,本文档加 confirm 增强)、§6.2 分离窗口同步 ⊂ CR-24/25(已写)。**状态: 待讨论→看板跟踪**。 + +**第一批 — 最高频痛点(每次对话都遇到)** + +- [ ] UX-2025-01 [P1] — **流式渲染中选中文字保持**(§2.1)。现象:AI 流式输出时选文字被 delta 打断丢失选区。方案 A(推荐):`selectionchange` + 选区在消息容器内 → 暂停 rAF 流式 parse → 选区结束恢复;方案 B:已完成块独立 DOM 节点不参与 v-html 替换。关联 ARC-08 块级 memo(splitBlocks 已按块切分,末块单独更新天然支持方案 B)— src/components/AiChat.vue + src/composables/ai/useAiEvents.ts +- [ ] UX-2025-02 [P1] — **消息操作栏(复制 / 重新生成)**(§1.2+§1.3)。AI 气泡 hover 显示操作栏:「📋 复制」+「🔄 重新生成」。复制 = F-260615-13 合并实现;重新生成:后端删最后一条 AI 消息 + 用倒数第二条 user 消息重跑 agentic loop(新增 `ai_regenerate` IPC)。代码块额外 hover 浮出「📋 复制代码」按钮(§2.2 子项)。— src/components/AiChat.vue + src-tauri/src/commands/ai/commands.rs +- [ ] UX-2025-03 [P1] — **错误气泡操作入口**(§4.1)。错误气泡底部增加按钮区:「🔁 重试」(取上一条 user 消息重发)+「⚙ 去设置」(仅 auth/provider 错误时显示,跳 Settings→AI Tab)。后端 `AiError` 加 `error_type: Option` 枚举(auth/network/timeout/provider_config/unknown),前端据此显隐按钮。— src-tauri/src/commands/ai/mod.rs(AiChatEvent) + src/api/types.ts + src/components/AiChat.vue +- [ ] UX-2025-04 [P1] — **断线保文**(§4.2)。流式中断时已接收文本不丢弃:`stream_llm` mid-stream error 改返回 `Some(partial_text, ...)` + `incomplete: true` 标志;agentic 收到 incomplete → 正常入库 + emit AiCompleted(非 AiError)+ 追加系统提示「⚠ 响应因网络中断不完整」;前端 AI 气泡底显示「继续生成」按钮。关联 B-260615-05(流尽空内容报错)同改 stream_recv.rs。— crates/df-ai/src/stream_recv.rs + src-tauri/src/commands/ai/agentic.rs + src/components/AiChat.vue + +**第二批 — 显著提升日常效率** + +- [ ] UX-2025-05 [P2] — **代码块语法高亮 + 复制按钮**(§2.2)。集成 highlight.js(按需加载不阻塞首屏),marked renderer code 回调调 `hljs.highlightAuto`;代码块右上角 hover 浮出「📋 复制代码」。依赖 ~30KB(highlight.js core + 常用语言包)。— src/composables/useMarkdown.ts + 新依赖 highlight.js +- [ ] UX-2025-06 [P2] — **对话搜索**(§3.1)。侧栏 header 加搜索框(实时过滤 conversations 匹配 title);搜索时取消分组平铺展示;支持 `Ctrl+K` 快捷键聚焦。— src/views/(AiChat|Sidebar).vue +- [ ] UX-2025-07 [P2] — **键盘快捷键**(§7.1)。Ctrl+N 新建 / Ctrl+K 搜索对话 / Ctrl+L 清空 / Ctrl+Shift+C 复制 AI 消息 / Ctrl+R 重新生成 / Esc 关闭面板 / Ctrl+B 切侧栏。全局监听 AiChat 聚焦时生效。— src/components/AiChat.vue +- [ ] UX-2025-08 [P2] — **新建对话 confirm 弹窗**(§3.4 增强)。生成中点新建对话时弹 ConfirmDialog(「当前正在生成,确定中断并新建?」),确认后才执行 B-260615-10 的软复位逻辑。防误触中断。— src/components/AiChat.vue + src-tauri/src/commands/ai/commands.rs + +**第三批 — 锦上添花** + +- [ ] UX-2025-09 [P2] — **编辑已发送消息并重新生成**(§1.1)。用户气泡 hover 显示"✏️ 编辑",点击回填输入框;发送时后端:①替换目标 message content ②后续所有消息标记 `status='truncated'`(软删除,保留历史可追溯,与 WF-A soft_delete 模式一致)③用 status='active' 的历史重跑 agentic loop。约束:只允许编辑最后一条 user 消息(中间编辑语义复杂+易乱上下文);generating 中拦住。前端:截断消息从视图移除,loading 等新回复。后端新增 `ai_chat_edit(message_id, new_content)` IPC。— src/components/AiChat.vue + src-tauri/src/commands/ai/(commands.rs|conversation.rs) + df-storage(messages 表加 status 列) +- [ ] UX-2025-10 [P2] — **@ 实体引用**(§1.4)。输入框支持 `@` 触发实体联想浮层(复用 `/` 技能联想 popover 架构);联想源:项目列表/任务列表/最近文件;选中展开为 `[项目: xxx]` 标记文本,后端 system prompt 注入上下文摘要。关联 F-260614-02(技能联想)。— src/components/AiChat.vue + src-tauri/src/commands/ai/prompt.rs +- [ ] UX-2025-11 [P2] — **输入框高度提升**(§1.5)。textarea 最大高度 120px→200px(~10 行),超过后再内部滚动。或可拖拽 resize handle。— src/components/AiChat.vue +- [ ] UX-2025-12 [P2] — **历史消息分页懒加载**(§2.3)。⏸️ **暂缓**:当前典型对话 <100 条消息,v-for 全量渲染无卡顿;等用户反馈长对话卡顿再做。switchConversation 首次加载最近 50 条,滚顶加载更多(需后端 offset/limit 支持 messages 查询)。或前端虚拟滚动替代。关联 B-260629(messages 无上限)。— src/stores/ai.ts + src/composables/ai/useAiConversations.ts + 后端 conversation.rs +- [ ] UX-2025-13 [P2] — **消息时间戳**(§2.4)。气泡下方极小字号(dim 色)展示相对时间(复用 time.ts 已有 common.justNow 等 key);hover tooltip 展示完整时间。— src/components/AiChat.vue +- [ ] UX-2025-14 [P2] — **Provider 切换反馈**(§5.1)。cycleProvider 切换后 toast 提示 + 0.15s 淡入动画 + bar 展示 model 名称小字。— src/stores/ai.ts + src/components/AiChat.vue +- [ ] UX-2025-15 [P2] — **技能参数用法展示**(§5.2)。联想浮层每项增加 argument_hint 等宽小字提示;选中技能后输入框上方 chip 展示完整参数格式说明。关联 F-260614-02(技能联想)。— src/components/AiChat.vue +- [ ] UX-2025-16 [P2] — **侧栏宽度可拖拽**(§6.1)。侧栏右边缘 2px 拖拽条(cursor: col-resize),范围 120~280px,宽度持久化 df-ai-ui 设置。— src/components/AiChat.vue (sidebar CSS) +- [ ] UX-2025-17 [P3] — **对话置顶**(§3.2)。ai_conversations 表加 pinned 字段;排序改为 pinned DESC, updated_at DESC;侧栏 hover 图钉按钮。— df-storage + src/views/Sidebar.vue + src/stores/ai.ts +- [ ] UX-2025-18 [P3] — **对话导出**(§3.3)。后端新增 ai_conversation_export(conv_id, format) 支持 markdown/json/txt;前端侧栏 hover 导出按钮。Markdown 格式:## 用户 / ## 助手交替 + 代码块保留围栏。— src-tauri/src/commands/ai/conversation.rs + src/views/Sidebar.vue +- [ ] UX-2025-19 [P3] — **消息列表虚拟滚动**(§7.2)。⏸️ **暂缓**:同 UX-12,当前数据量无需虚拟滚动;与 UX-12 合并为一条观察项,等用户反馈长对话卡顿再启动。长对话几百条消息全量渲染卡顿;集成 vue-virtual-scroller 或自研 IntersectionObserver 懒渲染;流式最后一条始终保持挂载。— src/components/AiChat.vue + 新依赖 +- [ ] UX-2025-20 [P3] — **空状态引导 + 标题过渡**(§7.3+§7.4)。空态展示 3-4 示例问题卡片(点击自动填入发送);无 provider 时引导配置;标题生成后 0.3s 淡入动画 + AI 生成标识图标。— src/components/AiChat.vue + +### 🟡 AI Chat 授权体验改进(2025-07-15 方案 → 待办化) + +> 来源:[aichat授权体验改进方案-2025-07-15.md](./02-架构设计/aichat授权体验改进方案-2025-07-15.md)。6 痛点 + 8 改进项,按原文优先级转待办。**状态: 待讨论→看板跟踪**。 + +**P0 — 快速改善体感(第一批建议落地,~2 天工作量)** + +- [ ] AE-2025-01 [P0] — **批量审批**。同一轮多 pending 时,ToolCardList 顶部显示「全部批准(N) / 全部拒绝」按钮。改动:①`ToolCardList.vue` 加批量操作栏(监听 pending_approval 数量)②`useAiSend.ts` 新增 `approveAll(rejectAll)` 循环调 `ai_approve`。预估 0.5 天 — src/components/ToolCardList.vue + src/composables/ai/useAiSend.ts +- [ ] AE-2025-02 [P0] — **审批计数器 + 跳转**。输入框上方/header 显示 `⏳ N 项待审批` 徽标,点击跳转到第一个 pending 卡片。改动:①`AiChat.vue` header 区域加审批徽标 ②`ToolCardList.vue` 暴露 `scrollToFirstPending` 方法。预估 0.5 天 — src/components/AiChat.vue + src/components/ToolCardList.vue +- [ ] AE-2025-03 [P0] — **write_file diff 预览**。write_file 审批时若文件已存在,展示前后对比 diff 而非裸 content(content 可能几百行代码截断到 300 字符)。改动:①后端 `tool_registry.rs` write_file handler 执行前读旧文件返回 diff ②`ToolCard.vue` pending_approval + name=write_file 时渲染 diff 视图(红删绿增)。关联 F-260615-10(diff 工具)可复用。预估 1 天 — src-tauri/src/commands/ai/tool_registry.rs + src/components/ToolCard.vue + +**P1 — 增强控制力** + +- [ ] AE-2025-04 [P1] — **会话级授权(Session Trust)**。替代原"全局宽松模式"。核心:信任是上下文相关的——同一会话内用户批准过某类操作后,后续同类操作自动放行;换会话清空重审。模型:AiSession 加 `session_trust: HashSet`(内存态,随会话销毁);TrustKey 按操作+目标范围粒度(如 `Write { dir: "src/" }`、`Execute { dir: workspace }`)。流程:首次操作→pending_approval 用户批准→记录 TrustKey → 同会话后续同类操作命中则自动放行(消息流显示轻量提示 `🔓 自动放行: write_file(xxx)`)。**白名单收紧决策**:纯读取(list_*/read_*/list_directory)始终自动放行;创建/修改/绑定/覆盖写入/高危全部需审批或会话级授权放行(bind_directory 归类为修改操作;write_file 覆盖写入已出事故不放行)。**首批实现**:write_file + run_command(写→跑→看→改闭环最高频连续操作);create/update 后续按需加。改动:①`mod.rs` AiSession 加 session_trust 字段 + TrustKey enum ②`audit.rs` process_tool_calls 审批前查 session_trust 命中则跳过 pending ③审批通过时写 session_trust ④前端自动放行操作显示轻量 toast 提示。关联 AE-05(High 二次确认: 会话级授权的操作是否也需二次确认? 建议: 首次审批走二次确认,后续自动放行跳过)。预估 1.5 天 — src-tauri/src/commands/ai/(mod.rs|audit.rs) + src/components/AiChat.vue — **设计决策(2026-06-15)**: 目录级粒度(非文件级)、仅写+执行首批、Webhook 未来走独立 execution_token 链路不影响本设计 +- [ ] AE-2025-05 [P1] — **High 二次确认**。delete/purge/run_command 等 High 风险操作,批准后弹 inline 确认("确定要永久删除?此操作不可恢复"),二次确认才真正执行 ai_approve。可用现有 ConfirmDialog 组件。预估 0.5 天 — src/components/ToolCard.vue +- [ ] AE-2025-06 [P1] — **审批超时(人工审批场景)**。pending_approval 超时自动 reject(默认 5 分钟),防对话永久卡死。**决策:纯前端定时器**(审批依赖页面交互,页面关闭 auto-reject 合理;不改后端)。超时后 toast 提示「⚰ 审批超时,已自动拒绝: {tool_name}({reason})」+ 调 ai_approve(id, false)。Settings 可配超时常量(3/5/10min)。**范围限定**:仅覆盖人工审批场景。未来 Webhook/外部集成走独立 `execution_token` 预鉴权链路(不经过 pending_approvals),不影响本设计。预估 0.5 天 — src/composables/ai/useAiSend.ts + src/views/Settings.vue + +**P2 — 信息透明度** + +- [ ] AE-2025-07 [P2] — **Agentic 进度条**。消息区域底部显示循环进度(`🔄 循环 3/10 · ⏳2待审批 · ✅5完成`)。改动:①`AiChat.vue` 底部加进度指示条 ②`useAiEvents.ts` 处理 AiAgentRound 时更新进度。预估 0.5 天 — src/components/AiChat.vue + src/composables/ai/useAiEvents.ts +- [ ] AE-2025-08 [P2] — **审批历史面板**。独立页面展示 `ai_tool_executions` 表审计记录(时间/工具/风险/状态/决策者/参数摘要/结果摘要)。改动:①后端新增 `list_tool_executions` IPC ②前端新建 AuditLog.vue 表格视图。预估 1 天 — src-tauri/src/commands/ai/(commands|audit).rs + 新建 src/views/AuditLog.vue + ### 待澄清 / A-B 待定 - [ ] S-260614-01 — 「显示多开」需求待澄清 — 用户报"设置勾选显示多开但 AiChat 未显示",全 src grep 零命中,疑似旧版本/指分离窗口/想新增开关,待用户截图确认 (06-14) @@ -333,8 +466,64 @@ - [x] T-260614-12 — ~~df-ideas 死代码~~ ✅ WF-E 部分完成(capture.rs 删 CaptureInput/IdeaCapture 死码,保留 Idea/IdeaScores 共享实体;promotion/scoring/adversarial 内"两套 Recommendation/PromotionPolicy 死枚举"嫌疑 agent 未确认存在/保留为对外契约,本次未动,待复查)(06-14, commit 89da9fa) - [ ] F-260615-01 — **[P1 功能增强]** HumanNode 审批节点支持自定义选项 + 单选/多选类型。现状:config `options: Vec` 已支持任意数量(2/3/…数量扩展已通),但 `decision` 是单 `String` 仅单选语义,`options` 空=自由文本。增强目标:不止「同意/拒绝」二选一,可配置 N 个候选项 + 单选(single)/多选(multiple)两种类型。**改动面**:①`df-core/events.rs` `WorkflowEvent::HumanApprovalRequest` 加 `select_type`、`HumanApprovalResponse` decision 单值→多值(`decisions: Vec` 或保留 decision 兼容 + 加 decisions)②`human_node.rs` config 解析 `select_type` + 校验(多选时每项 ∈ options,可加 min/max 选中数约束)③IPC `approve_human_approval` 签名 ④前端 `stores/project.ts` approve + `api/types.ts` 事件类型 + 审批弹窗 UI(单选 radio / 多选 checkbox)⑤单测改断言 + 新增多选/超限测。**注意**:向后兼容现 single 调用方,`select_type` 缺省 = single — source:用户需求(06-15),crates/df-nodes/src/human_node.rs + src-tauri/src/commands/workflow.rs + src/stores/project.ts — **✅已实施(a3cccb070fe9c8821,2026-06-15,待commit)**:6 文件契约向后兼容(events.rs SelectType 枚举 Single/Multiple 缺省 Single+HumanApprovalResponse decision+decisions 双字段/human_node.rs 校验 single len==1·multiple len≥1·∈options/workflow.rs IPC 加 decisions+select_type Option 缺省兼容/types.ts/project.ts approve/ProjectDetail.vue checkbox 多选 UI)。cargo check 0 err/df-nodes 21 test(含 4 新增多选)/vue-tsc 0 err。主代理核查契约向后兼容 + 6 文件边界 - [x] F-260615-02 ✅(2026-06-15,待commit) — **[P1 功能]** task 详情查看。✅已实施:get_task_by_id IPC(复用 TaskRepo::get_by_id ok_or_else 转 Result)+lib.rs 注册+taskApi.get+/tasks/:id 路由+TaskDetail.vue(11 字段:标题/描述/状态/优先级/关联项目 router-link 解析名/分支标签/负责人/基础分支/工作流定义/创建更新时间;复用 constants/project 标签+formatDate+watch route.params.id 重载)+Tasks.vue 列表项 @click router.push;TaskRecord TS 类型已存在无需新增;主代理核查 cargo check 0 err/vue-tsc 0 err/git diff 6 文件边界干净。现状:`src/views/Tasks.vue` 仅列表,无独立 TaskDetail 视图/路由(grep 仅 `ProjectDetail.vue` 嵌套任务命中,无独立详情页)。需求:点击 task 查看详情。**数据模型已就绪**(`df-storage/src/models.rs:53` `TaskRecord` 12 字段:id / project_id / title / description / status / priority / branch_name / assignee / workflow_def_id / base_branch / created_at / updated_at)。**改动面**:①新建 `src/views/TaskDetail.vue` 视图 + 路由(`router/index.ts` `/tasks/:id`)②task 详情 IPC(`get_task_by_id`,核对 `commands/task.rs` 现有 IPC 是否已有,无则补)③`Tasks.vue` 列表项点击 → 跳详情 ④详情页字段展示(title/description 渲染、status/priority 标签、关联项目名解析 project_id→name、branch/assignee 信息、时间戳)⑤可选:详情页内编辑(`update_task` IPC 已存在,FR-D6)。— source:用户需求(06-15),src/views/Tasks.vue + src/router/index.ts + src-tauri/src/commands/task.rs -- [ ] F-260615-03 — **[P1 功能]** list 工具分页能力(list_projects / list_tasks / list_ideas + list_deleted 回收站)。现状:4 工具 `truncate(50)` 硬截断,无 offset/limit 参数,返回纯数组无 total/has_more 提示——超 50 条数据 AI 看不到**且不知被截断**。**已核对**(tool_registry.rs:134 / :151 / :164 / :378)。**后果**:80 项目找第 50+ 名后的 X → AI 回复「不存在」;100 任务总结只覆盖前 50;60 灵感评估静默漏 10。**方案(用户定)**:①schema 加 `offset`(默认 0)/ `limit`(默认 50,最大 100)参数 ②返回改 `{ items, total, returned, has_more }` ③AI 见 `has_more: true` 可再调 `offset=50` 翻页。**改动面**:`tool_registry.rs` 4 工具 handler(list_projects:127 / list_tasks:139 / list_ideas:156 / list_deleted:377)schema 加参数 + 截断改 offset/limit slice + 返回结构包对象;total 用截断前 `items.len()`(无需 repo 加 count)。**⚠️ breaking change**:返回 Array→Object,4 工具的 tool 描述(register 第 2 参)须同步更新告知新结构,否则 AI 仍按数组解析。— source:用户需求(06-15),src-tauri/src/commands/ai/tool_registry.rs:127-165,377-379 +- [ ] F-260615-03 — **[P2 最小修]** list 工具截断标志(原完整分页方案,对抗分析降级)。现状:4 工具 `truncate(50)` 硬截断,AI 不知被截断会回复「不存在」。**对抗分析裁决**:数据量未到 50 条线(projects=5, tasks=61),完整分页 breaking change 风险 > 收益 → **降级为最小修**:不改返回结构(non breaking),只在 truncate 时加 `truncated: true` 标志 + 工具描述注明"最多返回 N 条"。未来数据量到时再升完整分页(F-03-full)。**改动面**:tool_registry.rs 4 handler 截断处加 truncated bool 返回 — src-tauri/src/commands/ai/tool_registry.rs:127-165,377-379 — source:用户需求(06-15)+对抗分析裁决(06-15) - [ ] F-260615-04 — **[P1 UX]** read_dir / read_file 工具卡片连续时折叠/收起,提高信息密度。现状:AI 探查目录常连续调多个 read_dir + read_file(先列目录再读多个文件),每个结果独立卡片平铺,长列表/大文件内容占满屏幕,信息密度低。需求:相邻同类读取卡片支持折叠——默认收起只显摘要(如「read_dir: 12 项」「read_file: src/main.rs (234 行)」),点击展开看详情;或连续 N 个同类卡片归组折叠。**改动面**:①`ToolCard.vue` 加折叠态(`collapsed` ref + 摘要/详情双视图 + chevron 图标 + 高度过渡)②连续同类检测/归组(`ToolCardList.vue` 按 `tool.name` 分组,已有列表容器适合放分组逻辑)③摘要提取(read_dir 数项数 / read_file 文件名 + 行数,解析 result)④折叠态持久化(可选,localStorage 按 conv)。关联信息密度构想(memory: devflow-info-density-concept,卡片折叠是其中一环)。— source:用户需求(06-15),src/components/ToolCard.vue + src/components/ToolCardList.vue — **勘察完成(2026-06-15,wxflofhf2)**:feasible/plan 11 步跨 5 文件(ToolCardList/ToolCard/useAiSend/stores/ai/global.css)。risk 标低但实为 UX 新行为+改核心 ai 状态文件(useAiSend.ts/stores/ai.ts)+plan 细节有误(useAiSend composable 无 emit 方法)。**拆小或留待**:先做 ToolCardList 分组+单卡折叠摘要(限定不碰 useAiSend/stores),完整折叠交互归信息密度构想单独立项 +- [ ] F-260615-07 — **[P2 功能]** read_file 加 search 参数(单文件内关键词定位)。现状:read_file 仅 offset/limit 分页(tool_registry.rs:458-501),无原生搜索——AI 找关键词需多次分页扫描定位,token 浪费 + 多次 IPC。用户实测确认 offset 0基 + limit=1 单行精确读取可用,但定位靠 AI 多轮分页。**方案**:① schema 加 `search`(string,可选)参数 ② handler 内若有 search:读全文(1MB 限内)→ 按行过滤含 search 的 → 返回 `matches: [{line, content}]`(line 为 1基行号,须在描述注明与 offset 0基的差异)③ 匹配超量:默认返前 50 匹配 + total + has_more。**改动面**:tool_registry.rs read_file handler(:458-501)schema 加参数 + 返回结构扩展(有 search 返 matches,无 search 走原 content 路径,两路径并存)。**权衡**:vs 独立 grep 工具——read_file search 满足单文件定位主场景(最小改动,复用现有工具);跨文件搜索待 B-260615-37(run_command stdout)修后用 grep/findstr 验证够不够用,不够再立 grep 工具(对标 Claude Code Grep)。**大小写**:默认区分(确定性优先),case_sensitive 参数先不加(做减法)。— source:用户建议(06-15),src-tauri/src/commands/ai/tool_registry.rs:458-501 + +### 🟡 文件操作工具集补全(2026-06-15 能力梳理·设计定稿 V2) + +> 用户给出 5 个工具具体 API 设计。**核心原则不变**:专用工具补 run_command 做不好/不安全的;系统命令类交给 run_command(B-37 修后)。**修正**:search_files 从"run_command 覆盖"升级为"值得做"(结构化 JSON > parse find 输出)。 + +**5 工具设计(用户方案)**: + +- [ ] F-260615-06 [P0] **patch_file(edit_file)** — → 见下方对抗分析区 :503 完整条目(含设计文档指针) +- [ ] F-260615-07 — **[P1] search_in_file**(内容搜索)— `path, keyword, (可选)regex, case_sensitive` → `[{line, content, column}]`。纯子串搜索先做(regex/case 后加,做减法)。替代 AI 多轮分页扫描 — tool_registry.rs read_file handler 扩展 +- [ ] F-260615-08 [P1] **file_info**(元信息)— `path` → `{exists, size, lines?, modified, is_binary, is_dir}`。**不读内容**,快速元数据。lines 用 `\n` 计数(不全文读);二进制不报 lines — tool_registry.rs 新增 +- [ ] F-260615-09 [P1] **append_file**(追加写入)— `path, content` → `{bytes_written, new_size}`。日志追加/增量写入,避 read-merge-write 竞态 — tool_registry.rs 新增 +- [ ] F-260615-12 [P1] **search_files**(文件名/glob 搜索)— `path, pattern: "*.ts", recursive?` → `[{path, size}]`。结构化 JSON > parse find 输出(AI token 效率+可靠)。从"run_command 覆盖"升级(原归类为不做的系统命令类) — tool_registry.rs 新增 + +**后续(P3/合并)**: + +- [ ] F-260615-10 [P2] **diff**(修改前对比,防误覆盖)— 并入 patch_file 增强(edit 前 return diff 给审批卡看清改动),或独立工具 — 关联 F-06 +- [ ] F-260615-11 [P3] **write_file base64**(二进制写入)— 图片/PDF/Excel,场景窄 — tool_registry.rs:519 + +**run_command 覆盖(不单独做)**:压缩/权限/软链/校验/批量 +**不做**:文件监听(app 层)/文件锁定(并入 F-06 Mutex) + +— source:用户 API 设计 V2(06-15),F-06 设计已定稿见 [patch_file工具设计-2026-06-15.md](./02-架构设计/patch_file工具设计-2026-06-15.md) + +### 🟡 对抗分析裁决(2026-06-15 十域十八项交叉验证) + +> 来源:用户对抗分析报告(10 域聚类 + 正反双向验证 + 依赖链提取)。**核心价值:6 个否决/暂缓裁决**("不做"比"做什么"更值钱,避免浪费投入)。完整报告见 docs/02-架构设计/对抗分析裁决-2026-06-15.md(若已落盘)。 + +**✅ 推进(第一批独立可并行)**: + +- [ ] F-07 trait 下沉 df-ai-core — 设计完备 4 项决策全定稿,退路可放 df-core,解锁 F-03/F-01 +- [ ] R-PD-2 ScriptNode 不注册 script — 3 行删除封死攻击面,工作流当前纯演示无真实脚本需求 +- [ ] F-260615-06 [P0] **patch_file(edit_file)** — 局部文件更新工具,补齐 AI 文件操作闭环。完整设计见 [patch_file工具设计-2026-06-15.md](./02-架构设计/patch_file工具设计-2026-06-15.md)(API/三层防御/边界情况/替代方案否决/实施步骤)。**核心**: old_text 精确匹配为主+line 辅助+Mutex 并发安全+expected_hash 指纹防脏写。第一批实现核心三件套(~50行)。— src-tauri/src/commands/ai/tool_registry.rs 新增 handler +- [ ] ARC-06 composable 循环依赖 — 提 aiShared.ts 破环最小改动(或合回 stores/ai.ts 备选) +- [ ] CR-09 .ai-md 样式 5 份→全局 css 抽取 — 零风险减法 +- [ ] F-09 status 对齐前后端 — 删从未产生的 planning/in_progress 等,前端 map 改 {active/archived} +- [ ] F-260615-03 最小修(截断标志)— **从完整分页降级**:不改返回结构(non breaking),truncate 时加 `truncated: true`(→ 见上方 :469 完整条目,本条为对抗分析区引用,勿重复实施) +- [ ] R-PD-9 display_hint 轻量抽取 — audit 工具名→文案映射作 display_hint,消除双份映射 + +**🟡 暂缓 / 需重设计**: + +- [x] T-11 条件引擎 Phase1+2 🟡 **暂缓** — 引擎设计好但**无前端 UI 消费方**(demoDag edges 无 condition 字段,用户无法配置条件)。等工作流有真实使用场景(BuildNode 落地后跑构建→测试→部署)再推进。决策点 A/B/C(手写求值器/复活 set_skipped/default false+warn)均认可 +- [x] F-260615-03 完整分页 🟡 **暂缓** → 降为最小修(truncated 标志),见上方 +- [x] ARC-05 project.ts 上帝 store 拆分 🟡 **需重设计拆分策略** — 不能简单按领域拆(会重蹈 ai.ts 循环依赖覆辙),需先把越层 invoke 沉到 api/workflow.ts。ARC-06 先做 + +**❌ 否决(人定"不做",不进路线)**: + +- [x] ~~R-PD-9 agentic loop 下沉 df-ai~~ ❌ **否决** — 与 ai.rs 子 module 拆分决策冲突("下沉需动 crate 依赖图引入循环依赖")。保留 display_hint 轻量部分 +- [x] ~~R-PD-8 DTO 映射层~~ ❌ **否决** — 过度设计,5 个 IPC 手动对齐可接受。替代:types.ts 头部文档约定"后端改 schema 必须同步改此文件" +- [x] ~~F-09 拆双字段(status 生命周期 + stage 开发阶段)~~ ❌ **否决** — 阶段概念已被弱化(df-stages 砍了阶段插件),DB 实际只有 active/archived/deleted,双字段过度设计 +- [x] ~~R-PD-12 做实 run_workflow AI 工具~~ ❌ **否决(当前)** — 工作流是内部基础设施,无真实 CI/CD 需求。删假能力(no-op 桩返 note)更诚实 + +**⚡ 推荐推进顺序**: +1. 第一批(独立并行): F-07 / R-PD-2 / F-15-06 edit_file / ARC-06 / CR-09 / F-09对齐 / F-03最小修 / R-PD-9 display_hint +2. 第二批(依赖第一批): F-03 对抗评估接 LLM(F-07后) / F-01 模型能力Phase1(F-07后) / R-PD-12 删假能力(R-PD-2后) +3. 第三批(需用户输入): S-01 API Key 401 / 显示多开 / 审批可见性 ## 已完成