docs: 巡检简报+todo 回写(2026-06-15 第2轮)

巡检发现:
- 2196c77 workflow 整文件替换回退破坏(AiChat+Ideas 12项功能)
- B-260615-03 truncated 标志已落地
- AR-8-scroll scrollToBottom smooth 已补
- CR-260615-09 .ai-md 残余:4详情页各21处 scoped .ai-md
  (全局 ai-md.css 75行已建,旧副本待清理但非阻塞)
This commit is contained in:
2026-06-15 17:23:57 +08:00
parent 672d677046
commit f30df333b3
17 changed files with 2184 additions and 15 deletions

View File

@@ -0,0 +1,295 @@
# AIChat 交互体验改进方案
> 创建: 2025-07-15 | 状态: 待讨论
> 范围: 消息发送、流式渲染、对话管理、错误恢复、技能/Provider、窗口布局等非授权类交互
---
## 一、消息输入与发送
### 1.1 无法编辑已发送消息
**现象**:用户发出消息后发现措辞有误,只能重新打一条新消息。无法像 ChatGPT/Claude 那样编辑上一条用户消息并重新生成。
**根因**`sendMessage`useAiSend.tspush 后的消息是只追加不可变的。前端没有编辑入口,后端 `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<ErrorType>` 枚举(`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 空状态/标题 | 打磨细节 |

View File

@@ -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` 续生成)。
**影响**:用户处于"被动等待"状态,无法并行做其他事。
### 痛点 3Medium 和 High 体验无差异
**现象**:两者都弹同样的审批卡片、同样的按钮,交互流程完全一致。
**根因**`process_tool_calls`audit.rs:103-120中 Medium 和 High 走同一个 `pending_approvals.insert` 分支,区别仅在 `build_approval_reason` 的文案后缀("请确认是否执行" vs "高风险,需人工批准")。
**影响**High 操作(如 `purge_project` 不可恢复)缺乏足够的警示力度,容易误操作。
### 痛点 4write_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_fileworkspace 内自动放行)
☑ 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 天工作量,覆盖最高频的体验痛点。

View File

@@ -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<String>,
/// 有序补丁列表(从文件末尾往前执行,避免行号偏移)
patches: Vec<Patch>,
}
/// 单个补丁
struct Patch {
/// 必填:要替换的旧文本(精确匹配 = 乐观锁)
old_text: String,
/// 必填:替换后的新文本
new_text: String,
/// 可选:行号辅助定位(快速跳转 + 去歧增强)
/// 有值时优先跳到该行检查 old_text不匹配则降级全文扫描
line: Option<u32>,
/// 可选old_text 之前的上下文锚(去歧——多匹配时精确锁定)
before_text: Option<String>,
/// 可选old_text 之后的上下文锚(去歧)
after_text: Option<String>,
}
```
### 2.2 响应结构
```rust
struct PatchResult {
success: bool,
patches_applied: usize, // 成功替换的 patch 数
total_matches: usize, // 每个 patch 的总命中数(含未替换的)
lines_changed: i32, // 总行数变化(正=增加 负=减少)
warnings: Vec<String>, // ["匹配到 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<Mutex<HashMap<PathBuf, ()>>> =
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 通过 → 执行 patchL2 old_text 校验 + L3 .bak
```
### 决策 5截断策略 — 软删除标记(非真删)
关联 UX-2025-09 编辑消息功能。patch_file 本身不涉及消息截断,但设计原则一致:
```
messages 表: status 列
active — 正常显示
truncated — 被编辑截断(前端不展示,后端不进 context
保留历史可追溯,与 WF-A soft_delete 模式一致
```
---
## 四、三层防御架构
```
┌─────────────────────────────────────────────┐
│ L1: 文件级 Mutex │
│ 防时机冲突:同文件读写自动串行化 │
│ 实现: HashMap<PathBuf, Mutex<()>> (~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) | 过度工程;需每语言 parserAI 代码未必能 parse |
| 纯 line 号编辑 | 无内容校验=并发不安全;行号漂移易出错 |
| 全局文件队列 | 单用户桌面不需要Mutex 够用且简单 10x |

View File

@@ -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-12devflow 语境「决策/决策需求点」= 日常开发功能细节取舍(为什么这么定),**非** 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 排查教训

View File

@@ -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<String>`。前后端契约对齐。 | 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/Failedforward 任务永远 `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.vue362 行新文件)— 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 半硬编码 |
| ✅ 亮点 | — | XSSDOMPurify + escapeFallback 双层)/ `void mdReady` 响应式依赖捕获(注释明确 B-24 修复点)/ Promise.all 并发 / onMounted 不阻塞 load |
### 2. 后端提交 `2de0c6e`16 文件 1418+/152-)— 工程化重构扎实1 真实 P0 + 文档完善项
| # | 等级 | 位置 | 发现 |
|---|---|---|---|
| ① | 🔴 | stream_recv.rs:97-1082de0c6e 版) | 注释虚假承诺「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` 下沉 aiShareduseAiStream 注释明确「本模块不再 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 个真实 P0B-35、确认 1 个架构亮点落地aiShared 破环)。无运行时 panic/数据损坏/并发竞争类硬伤,后端重构质量评级**良**。
todo 映射B-260615-35新增CR-260615-09 描述更新四→五份B-260615-34 标 ✅B-260615-32/33 加复核标注。

View File

@@ -0,0 +1,123 @@
# 定时代码走查 — 第 2 轮2026-06-15
> 触发:定时走查 cron每 30 分钟)。本轮范围 = 自上次走查起新增 6 提交(`fddca9d`/`892a642`/`19d64fc`/`d809cf4`/`8710d6c`/`06a2dea`)前端改动 + 工作区未提交(`types.ts`/`project.ts` M
> 方法3 路后台代理并行AARC-05 store 拆分 / BB-22 状态同步含 B-32/33 核对 / CCR-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 至此全部闭环。
---
## 新发现 P0AR-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 子 storestate/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` IPCcommands.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<string,unknown>` 类型收窄
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-19action 字段 emit 但前端不消费,契约冗余)/ CR-20stopDataChangedListener try/catch 过度防御,与 workflow.ts:108 不一致)。
---
## P0 汇总(仍未修)
| ID | 问题 | 状态 |
|---|---|---|
| B-260615-35 | broadcast Lagged 兜底仅 warn → 终态事件丢失前端永久卡死(后端 workflow.rs:90-110 | **未修**第1轮发现本轮确认仍在|
| AR-11 前端 | listener 永不 attachApp.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-11todo:77状态更新 / CR-11todo:212标 ✅ / 新增 CR-260615-18/19/20。

View File

@@ -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 attachdc27e79
- `App.vue:218` onMounted `await projectStore.startDataChangedListener()` + `:258` onUnmounted `projectStore.stopDataChangedListener?.()`
- barrel `listen<DfDataChangedPayload>('df-data-changed')` 按 entity 调 loadProjects/loadTasks/loadIdeas
- 后端 audit.rs `data_change_for_tool` 映射create_project/idea/task→createupdate_*→updatedelete_*/purge→deleterestore/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/20f574562
- **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/catchP3 轻量)|
**本轮价值**:确认前两轮走查发现被接收方完整修复(含 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 新增。

View File

@@ -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-CNloadAll 后回填,已知短暂态有注释。
- **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 整页未接 i18n13 处)是新页面 i18n 债。
**方法论**换垂直切片角度i18n 维度)有效突破前几轮按提交/改动的横切盲区,发现 40+ 处硬编码 — 印证 task2/3「功能/模块垂直走查」的价值(换角度有新发现)。
todo 映射CR-260615-08 扩展3→40+ 子项,标 P1-1 最高优先)+ CR-21 标 ✅。

View File

@@ -0,0 +1,94 @@
# 定时代码走查 — 第 5 轮2026-06-15
> 触发:定时走查 cron每 30 分钟)。范围 = **api 层垂直切片**9 文件此前从未深查IPC 契约边界。无新提交自第4轮起 src/ 干净)。
> 方法2 路后台代理Aapi invoke 契约层 / Btypes 类型对齐层)+ 主代理地面核对 + **WebSearch 权威裁决**。
> 性质:核对记录,不改产品代码(本会话 session-role-diagnose-only。todo 待修项见 `docs/todo.md`。
> 关联:[第1-4轮](./)。
---
## 🚨 核心发现Tauri v2 camelCase 行为权威裁决(代理矛盾 → 纠正假阳性)
### 矛盾
- **代理 A**Tauri v2 **不转** camelCase6 处 IPC 参数遗漏ai_approve/ai_save_provider/knowledge_events/ai_set_concurrency_config/ai_set_provider/ai_pending_tool_calls真 P0B-34 同类)
- **代理 B**Tauri v2 **默认转** camelCase宏生成别名6 处全误判
### 权威裁决WebSearch Tauri v2 官方文档 + StackOverflow
> **Tauri v2 默认自动转换 snake_caseRust↔ camelCaseJavaScript**。
> 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 用 camelCaseworkflow.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 structIdea/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` **错**,后端 TaskStatustypes.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 不直碰 invokeworkflow.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-22types 注释/冗余 P3+ CR-260615-23B-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)

View File

@@ -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**refreshDashboard.vue:202-204Promise.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 进度条 + 数字 statGPU 友好动画,**无性能问题**
- aiShared 破环正确ARC-06nextMsgId 两窗口各自独立不冲突
---
## 📊 本轮摘要
| 类别 | 数 | 代表 |
|---|---|---|
| 价值闭环 | 1 | CR-22 ✅ + CR-23 撤销 |
| P1 新发现 | 1 | 分离窗口 R1+R2 listener 双注册+泄漏CR-24|
| P2 新发现 | 3 | 分离窗口状态机CR-25/ 列表页契约+空态CR-26/ DashboardCR-27|
| 亮点 | 4 | 列表页 i18n 100% / Dashboard 纯 CSS 无性能问题 / aiShared 破环 / useConfirm DRY |
**本轮价值****分离窗口是新功能,前几轮横切(按提交/改动)完全没覆盖**垂直切片Dashboard+AI窗口维度首次深查即暴露 P1listener 泄漏)+ 状态机缺口。**再次印证 task2/3 垂直走查价值** — 横切收敛后垂直切片是新发现来源i18n/api/列表页/AI窗口 4 个垂直切片均挖出真问题)。
**方法论**:分离窗口 P1R1+R2经地面核 stopListener 零调用坐实避免第5轮代理 A 假阳性教训),走查发现须地面验证。
todo 映射CR-260615-24P1 分离窗口 listener/ CR-25P2 状态机)/ CR-26P2 列表页)/ CR-27P2 Dashboard

View File

@@ -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 通道断P1CR-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 缺口:
- AiCompleteduseAiEvents: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 落库,静默不持久化。当前无 buguseSetting 多绑值类型df-ai-ui 走 set 整对象替换),隐式契约。
---
## 📊 本轮摘要
| 类别 | 数 | 代表 |
|---|---|---|
| 价值闭环 | 3 | CR-24/26/27 全修(修复质量好)|
| P1 新发现 | 1 | knowledge error 通道断CR-28CR-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 个垂直切片storesknowledge error 通道断是新发现维度(超越 i18n 的功能缺陷。ai.ts 状态机完整是「走查验证修复」价值(非只找 bug
todo 映射CR-260615-28P1 knowledge error/ CR-29P2 messages+竞态)/ CR-30P3 useSetting 边界)。

View File

@@ -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 恢复 panelOpenR3+ resumeInDetached 核 ai_is_generatingR4+ 删 switchConversation 自动 switchR5|
| 40461b2 | CR-28 ✅ | knowledge create 内 loadCandidates 改非阻塞(不污染成功语义)+ Knowledge.vue error banner 消费 store.error**通道接通**|
| b300bb7 | CR-30 ✅ | useSetting 值类型契约文档 + changed helper JSON 深比较 |
**CR-29ai.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-29ai.ts messages 无上限 + knowledge 竞态)/ CR-31useConfirm/ToolCardList 健壮性)/ CR-08i18n 硬编码 40+/ CR-09/10DRY .ai-md/renderedRef/ CR-12可选一组
---
## 💡 建议
前端走查已**充分覆盖**8 轮 + 5 垂直切片,收尾无必修,接收方修复速度跟上发现速度)。建议:
1. **停止 cron**(前端边际收益递减)— 用户可 `/loop` 停止或让我 CronDelete
2. **转 task2/3 后端 workflow 功能/模块垂直走查**(新维度:后端 src-tauri + 8 crate 全链路,此前前端走查未覆盖后端)
todo 映射CR-260615-31P2 收尾健壮性)。

View File

@@ -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 |

View File

@@ -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 模块文档列不存在的文件 + 已删 traitA-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 项零入 todoC-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 头号 P0human_node.rs:41 缺 awaitB-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→实际 falseB-260614-02:138 try_recv_human_approval已删:155-156 set_waiting/set_skipped已删仅留 set_cancelled。**建议**4 处对齐。
### 10. df-storage V8→V13B-A-4/C-4.1
:3,9,26-36,114,132 反复 V1-V8/MIGRATION_VERSION=8实际 V1-V13V9 ai_providers/V10 knowledges.reasoning+knowledge_events/V11 projects.deleted_at/V12 path+stack/V13 app_settings。表数 11→18Repo 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 仅 ShellD-3.3
ARCHITECTURE:84/PROGRESS:29/df-nodes 文档描述 df-execute Shell/Docker/SSH/Git实际仅 shell.rs+lib.rsdocker/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。**建议**:统一「原 132026-06-14 删 5 零引用,现 8」ADR 加「已修正」标注保留决策史)。
### 14. INDEX 漏 9 文档 + 09-问题排查目录A/D-2.3)— **本会话已补正**
F-07 trait下沉/密钥迁移/工作流边界/条件引擎/generating状态机02 区 5/DAG引擎详解/AI对话引擎03 区 2/全局代码review05 区 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 个 P0AC3/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-V13C-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「文档核对待修项」

View File

@@ -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-问题排查/) | 排查记录、根因分析 |
---

189
docs/ai-file-ops-manual.md Normal file
View File

@@ -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 |

View File

@@ -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 keyTS2589 用 as any 规避 vue-i18n 深度泛型)(commit 9e2aeff)
- [x] AR-10 ~~想法→灵感迁移残留~~ ✅ 已统一13 文件批量i18n zh-CN + 后端错误 + LLM 描述/提示词 + store toasten 待定 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<String>`,前后端契约对齐。**纠正上轮过时判断**(上轮压缩前快照仍 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/687title清空/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``<AiMarkdown>` 组件。B-24/25 复制粘贴源头 — 详见走查 ⑤
- [ ] CR-260615-09 — **[P1]** 份 .ai-md 样式逐字重复 ~350 行(AiChat/ProjectDetail/Ideas/Knowledge/TaskDetail) → 抽全局 `src/styles/ai-md.css``<AiMarkdown>` 组件。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) 只解构 entityaction 忽略,刷新统一全量 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 passtodo 零条目)— 已完成区补汇总 — 详见 §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 未做子项拆独立 todoaudit.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/16INDEX 漏列部分本会话已补,见 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``<AiMarkdown>` 组件。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/687title清空/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 /CPS 语法引号嵌套易错)。**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 用法,非 bugb) 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 块级 memosplitBlocks 已按块切分,末块单独更新天然支持方案 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<ErrorType>` 枚举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 浮出「📋 复制代码」。依赖 ~30KBhighlight.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 等 keyhover 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 而非裸 contentcontent 可能几百行代码截断到 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>`内存态随会话销毁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<String>` 已支持任意数量2/3/…数量扩展已通),但 `decision` 是单 `String` 仅单选语义,`options` 空=自由文本。增强目标:不止「同意/拒绝」二选一,可配置 N 个候选项 + 单选(single)/多选(multiple)两种类型。**改动面**:①`df-core/events.rs` `WorkflowEvent::HumanApprovalRequest``select_type``HumanApprovalResponse` decision 单值→多值(`decisions: Vec<String>` 或保留 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 任务总结只覆盖前 5060 灵感评估静默漏 10。**方案(用户定)**①schema 加 `offset`(默认 0/ `limit`(默认 50最大 100参数 ②返回改 `{ items, total, returned, has_more }` ③AI 见 `has_more: true` 可再调 `offset=50` 翻页。**改动面**`tool_registry.rs` 4 工具 handlerlist_projects:127 / list_tasks:139 / list_ideas:156 / list_deleted:377schema 加参数 + 截断改 offset/limit slice + 返回结构包对象total 用截断前 `items.len()`(无需 repo 加 count。**⚠️ breaking change**:返回 Array→Object4 工具的 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-501schema 加参数 + 返回结构扩展(有 search 返 matches无 search 走原 content 路径,两路径并存)。**权衡**vs 独立 grep 工具——read_file search 满足单文件定位主场景(最小改动,复用现有工具);跨文件搜索待 B-260615-37run_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_commandB-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 / 显示多开 / 审批可见性
## 已完成