squash合并: - 意图识别层论证(8维度+10业界佐证) - 多主题上下文管理愿景+并存论证+补充论证(多轮agentic) - 架构设计文档物理分类(四子目录+INDEX+命名规范+引用同步+边界清晰化) - 前端架构技术债清单归档
16 KiB
架构审查报告(2026-06-15)
范围:Rust workspace(8 crate + src-tauri 汇聚层,14586 行)+ 前端(Vue3+Pinia,11830 行),共约 26k 行。 方法:2 个 general-purpose 子代理并行审「Rust crate 架构」「前端架构」,主代理直读 df-core/df-execute/coordinator/conditions 核实空壳与抽象层,并对 3 个 🔴 删代码/可见断言做 grep 复验(全部坐实)。 去重:与 全栈代码审查报告-2026-06-14.md(bug/性能 FR-*)、架构与缺陷复核报告-2026-06-14.md(复核+回归审计)、aichat审查报告-2026-06-14.md(AR 系列)互补——本报告只记架构层面(模块边界/依赖方向/抽象层次/扩展性/状态管理/技术债),不重复 bug、性能、aichat 专项项。 性质:dry — 仅审查 + 文档,不改代码。
§1 架构总览
1.1 Crate 地图(Rust)
| Crate | 职责 | 内部依赖 | 行数 |
|---|---|---|---|
| df-core | 纯类型定义(ID 别名/状态枚举/new_id)+ error + events | 无(根) | 429 |
| df-storage | SQLite 持久化(11 个 Repo + models + migrations) | df-core | 2522 |
| df-workflow | 工作流引擎(DAG/Node trait/Executor/Registry/StateMachine/EventBus/Conditions) | df-core | 1120 |
| df-ai | AI 编排(LlmProvider trait + OpenAI/Anthropic compat + 工具注册 + 上下文 + 流式) | df-core | 2588 |
| df-nodes | 3 个内置节点(AiNode/ScriptNode/HumanNode) | df-core + df-workflow + df-ai + df-execute | 773 |
| df-execute | 仅一个 shell::execute 函数(带 TODO) |
df-core | 76 |
| df-ideas | 想法池领域(capture/scoring/adversarial 启发式/promotion 占位) | df-core | 759 |
| df-project | 项目领域(create_from_idea / scan 技术栈探测) | df-core | 486 |
| src-tauri | 汇聚层:47 个 IPC 命令 + AppState + 跨 crate 业务编排 | 全部 8 crate | 5711 |
依赖图(无环,星型收敛):
df-core (零依赖根)
/ / | \ \ \
storage workflow ai ideas project execute
| | |
└───┴── nodes (唯一多依赖者)
|
src-tauri (依赖全部 8 crate)
除 df-nodes 依赖 df-workflow+df-ai 外,6 个 crate 间零横向依赖——业务域 crate(ideas/project)不依赖持久化层(storage),靠 src-tauri 在 IPC 层组合。
1.2 前端分层地图
| 层 | 关键文件 | 职责 | 备注 |
|---|---|---|---|
| stores | project(337) / ai(84) / knowledge(157) / appSettings(139) / settings(99) | 领域状态 | project 是上帝 store;settings 死代码 |
| composables/ai | 6 文件(events/stream/send/conversations/window/panel) | AI 业务逻辑 | 为拆而拆,循环依赖 |
| views | 9 个(4875 行) | 页面 | Ideas 929 / Settings 1032 偏重 |
| components | AiChat(1359) / ToolCard(760) 等 | UI 组件 | AiChat 巨型 |
| api | 8 文件(736 行) | Tauri invoke 封装 | 分层清晰 |
§2 架构级问题清单
🔴 结构风险(6)
① [df-execute] 空壳 crate + src-tauri 死依赖 — 应合并删除
现状:crates/df-execute/src/lib.rs:7 仅 pub mod shell;;shell.rs 全文 73 行只 1 个 execute 函数且带 TODO: 完整实现(:33)。全仓库唯一调用方是 df-nodes/src/script_node.rs:34,42。src-tauri/Cargo.toml:32 声明 df-execute 依赖,但 grep 确认 src-tauri/src/ 零处引用 df_execute(已复验)。
架构影响:一个 76 行/2 文件、仅暴露 1 函数的 crate,是过度拆分典型。其存在理由("执行运行时",原含 docker/git_ops/ssh)重构删除后已不成立。src-tauri 那条依赖是残留死代码。
建议:合并 df-execute 进 df-nodes(作 script_node 内部模块或 df-nodes::shell),删该 crate + src-tauri/Cargo.toml:32 死依赖。净减 1 crate + 1 死依赖。
② [stores/settings.ts] mock 上帝 store 死代码 — 零消费者
现状:stores/settings.ts:32-75 持硬编码 mock 数据(Anthropic/Zhipu/DeepSeek provider、5 连接、theme/dataDir),全写死展示字符串。grep 确认仅 stores/index.ts:6 导出,全 src 零组件 import useSettingsStore(已复验)。真实 provider CRUD 走 api/ai.ts + Settings.vue 本地 ref,真实偏好走 appSettings。
架构影响:双轨幻觉——同名 settings 一真(appSettings/SQLite)一假(settings.ts/mock),后者 AIProvider/Connection interface 还被 re-export,新人易误用。与「做减法」画像直接冲突。
建议:删 stores/settings.ts + stores/index.ts:6-7 导出;interface 若有外部依赖迁 api/types.ts。零风险减法。
③ [stores/project.ts] 上帝 store(四领域强耦合 + 越层 invoke)
现状:project.ts 同时管 projects/tasks/ideas/workflowExecutions/liveEvents/pendingApproval 六类 state(:9-25),方法覆盖四领域 CRUD + 事件监听 + 审批。project.ts:252 invoke('approve_human_approval')、:271 invoke('cancel_workflow_node') 直接调 invoke 绕过 api 层——全项目仅此一处 store 越层(其余 50+ invoke 全在 api/*.ts)。
架构影响:违反单一职责;workflow 审批逻辑寄生 project store;分层不彻底。
建议:拆 project/task/idea/workflow 四 store(至少 workflow+审批独立);approve_human_approval/cancel_workflow_node 沉 api/workflow.ts。
④ [composables/ai/] 6 文件为拆而拆 — events↔stream 循环依赖
现状:6 个 composable 全部 import { state } from stores/ai(共享单例),且互相模块级 import 形成环:useAiEvents.ts:18 → useAiStream(reset);useAiStream.ts:12 → useAiEvents(nextMsgId) ← 双向;useAiSend→useAiStream+useAiEvents;useAiConversations→useAiEvents+useAiPanel;useAiWindow→其余三者。useAiStore() 只是 ...useAiXxx() 展开,每个 composable 恰被一处展开、零外部复用。
架构影响:拆分未降耦合,反把一个内聚状态机(流式事件→看门狗→队列→对话)打散到 6 文件,靠 ES module 循环 import 维持。events↔stream 双向依赖靠函数提升侥幸可运行,属脆弱结构。
建议:要么承认是单 store 内聚逻辑、合回 stores/ai.ts(最简);要么把 nextMsgId/state 等共享提独立 aiShared.ts 破环。
⑤ [App.vue:273] /decisions 路由死链 — 用户可见坏链
现状:App.vue:273 secondaryNav 含 { path: '/decisions', label: 'nav.decisions' },但 router/index.ts 无此路由(已复验,仅 i18n 文案存在)。点击落空白/重定向。
架构影响:导航与路由不同步,用户可感。
建议:补路由+视图,或从 secondaryNav 删该项(务实减法)。
⑥ [前端] 模块级全局态散落 + 多窗口 localStorage 手搓同步
现状:跨组件状态大量用模块级 let/const 而非 store——useAiEvents.ts:27-32 四个、useAiStream.ts:18 watchdog、useAiWindow.ts:17-18、useAiConversations.ts:44 switchId、stores/project.ts:27,329。多窗口(主窗口+ai-detached)各独立 JS realm,模块级单例不跨窗口共享;useAiWindow.ts:30-31 用 localStorage.setItem('df-ai-gen'/'df-ai-text') 手动快照,分离窗口 resumeInDetached(:82-95) 读回——手搓跨窗口 IPC,仅覆盖 currentText 两字段。
架构影响:模块级 state 单窗口内是隐式全局,调试难追踪;「双窗口同时操作同一对话」竞态靠 localStorage 单字段兜不住。
建议:跨窗口状态显式走 Tauri event(emit/listen)而非 localStorage 快照;模块级私有态集中文档化。
🟡 改进(6)
⑦ [df-core] 名不副实——类型库而非核心
lib.rs:5 自述"核心类型定义",但 types.rs 全是 pub type XId = String 别名 + 状态枚举 + new_id(),无 trait/无抽象/无行为契约。真正核心 trait(Node 在 df-workflow、LlmProvider 在 df-ai)各自下沉到功能 crate。命名误导读者对其角色预期。建议:改名 df-types,或注释明确"只放跨 crate 共享数据类型,不放 trait"。低优先。
⑧ [src-tauri] IPC 层成事实业务编排层(5711 行)
crate 故意解耦(ideas 不依赖 project/storage),代价是「两 crate 怎么协作」无处安放,全部上浮 src-tauri。典型 commands/idea.rs:96-161 promote_idea:取 IdeaRecord → 调 df_project 构造 Project → 插 df_storage → 补偿删除 → 回写 ideas,其中 record_to_idea(:252)、分数 *10 缩放(:224)、JSON 组装(:208) 全在 IPC 文件。df-ideas/promotion.rs:82 的 do_promote 反而是空壳——领域逻辑被分置两处,领域 crate 残缺。src-tauri(5711) > 最大 crate(df-ai 2588),IPC 不是薄转发而是应用服务层。建议:中期引入 df-app 或 src-tauri 内 services/ 收编跨 crate 编排,让 IPC 回归薄转发;至少把 record_to_idea 这类纯映射下沉回 df-ideas。技术债,不紧急。
⑨ [src-tauri/commands/ai] AI agent loop 在 IPC 层而非 df-ai
commands/ai/ 11 文件 3438 行,含 agentic.rs(292) ReAct 主循环、audit.rs(342) 工具审计、knowledge_inject.rs(557) 提炼。df-ai 提供 trait + provider 实现,但「怎么编排多轮工具调用」这层智能在 src-tauri,使 df-ai 退化为「LLM 调用 SDK」。与 aichat 审查「单链 ReAct、coordinator 空壳」一致——智能层无处安放暂栖 IPC。建议:观察项,B 路线(多 agent 协作)立项时一并从 IPC 抽出,现不宜妄动。
⑩ [views] 3 个 view 绕 store 调 api
Projects.vue:160,163,187(scanStack/checkBinding/scanWithAi)+ ProjectDetail.vue:211 混用 store 与 api;Settings.vue:374,387 完全不走 store(provider CRUD 直接 aiApi + 本地 ref),自带 toast(:390) 与 App.vue 全局 toast 重复。分层不一致——同性质「读后端数据」有的进 store 有的留 view 本地。建议:scan/checkBinding 预览型只读可接受;Settings provider 列表应进 store(与 stores/ai.ts:39 providers 合一,避免双份真源);toast 二选一。
⑪ [stores/index.ts] barrel 漏导 useAiStore
stores/index.ts 导出 project/knowledge/settings/appSettings,漏 useAiStore(stores/ai.ts:73)。故 App.vue/AiChat.vue/AiDetached.vue 全部直连 stores/ai 文件绕过 barrel。barrel 形同虚设,ai store 成「特殊公民」。建议:补 export { useAiStore } from './ai'。
⑫ [api/types.ts] 前后端类型契约手写 + as any 绕过
api/types.ts:1 注释"与 Rust Record 严格对齐"但纯手写无代码生成。漂移风险点:stores/project.ts:234 payload.event as unknown as HumanApprovalRequest(字段存在性无校验);:73,145,182 (state.xxx[idx] as any)[field]=value(field 是 string 无约束);useAiConversations.ts:71 历史消息全程 (m:any)、:81 JSON.parse 无 try-catch;Settings/ToolCard 多处 as any。契约脆弱集中在「动态字段更新」和「事件 payload 强转」。建议:动态 field 改 keyof 约束或具名方法;payload 强转改类型守卫;长期考虑 ts-rs 代码生成。
⚪ 观察(4)
⑬ [df-ai/coordinator.rs:3 等] 空壳是 B 路线有意占位 — coordinator.rs:3 注释 ⚠ B 路线占位...勿删;df-workflow/conditions.rs:4 TODO: 实现完整条件表达式(当前只识 true/false 字面量,保守拒绝=安全);df-ai/router.rs:42 ModelRouter 全返 default_model(no-op);df-ideas/promotion.rs:67 SemiAuto TODO。coordinator 有明确归属保留;conditions/router 若长期无调用方依赖其"未来能力",可删减负(YAGNI)。决策性。
⑭ [AiSession] 单例 + generating bool 锁死多会话并发 — state.rs:164 ai_session: Arc<Mutex<AiSession>>(全局唯一),commands.rs:45,48 generating bool 充当全局对话互斥锁,state.rs:93-96 注释自承认"per_conv 是应用级单一信号量非 map,未来多对话并发需改 HashMap"。同一时刻全局只能有一个 AI 对话生成。是有意识的设计取舍,符合单链 ReAct 现状,但是异步审批/多会话(见 memory devflow-async-approval-concept)的结构前置障碍。B 路线范畴,现不阻塞。
⑮ [AppState] God Struct — state.rs:135-185 含 15 个 Repo + event_bus + registry + ai_tools + ai_session + knowledge_config + llm_concurrency + workflow_state_registry。所有 IPC 共享巨型 State,无边界隔离。桌面应用规模可接受,若 IPC 做 service 化(⑧)State 可随之分组。
⑯ [前端] IPC 命名风格不统一 — 命令名 snake_case(list_projects)/ai_前缀(ai_chat_send)混存;参数 camelCase(convId)vs snake(projectId)混。新增 IPC 需改 api+store+view 约 3 处,散弹度可接受。建议:命令名统一 snake_case,参数统一 camelCase(Tauri 自动转)。
§3 亮点
- 扩展点单点注册,抽象得当:新增工作流节点改 2 处(df-nodes 加 impl Node +
state.rs:225 build_registry注册一行);新增 AI 工具改 1 处(tool_registry.rsregister);新增 provider 改 1 处(df-ai/lib.rs:21 build_providermatch)。Registry/工厂用最朴素 HashMap,没搞插件动态加载那套过度抽象。架构最健康的一面。 - 依赖图无环、星型收敛:crate 间零横向耦合,编译/测试隔离好,6 crate 不互相牵连。
- 空壳占位有显式标注:coordinator 的 B 路线注释、conditions 的保守拒绝设计,非失控,是有意识的路线占位。
- 前端减法实践:
useConfirm(抽 4 视图重复)、constants/project(统一状态映射)、utils/time(根治 Invalid Date)——好的减法。 - i18n/路由/api 分层清晰:i18n 按模块分文件 zh/en 对称,路由全懒加载带 meta,api 层 8 文件按领域分 + barrel 统一导出(除 ③ project store 越层、⑪ ai store 漏导两处瑕疵)。
- 工程素养细节:LlmConcurrency 双层 Semaphore、promote_idea 补偿事务删除、FR-S7/S8 安全防护——显工程功底。
§4 总评
架构成熟度:中等偏清晰(Rust 7/10,前端 6/10)。
核心张力(1 个真问题 + 几个取舍):
- 真问题:df-execute 空壳化 + src-tauri 死依赖(①),应做减法合并。这是唯一违背「做减法」画像的硬伤。
- 核心张力:crate 刻意解耦 → 跨 crate 编排全部上浮 src-tauri → IPC 层 5711 行成事实业务层(⑧⑨)。这是「高内聚低耦合」的代价,当前规模可忍,是中期技术债。
- 前端回潮:AI 模块过度拆分(④)、project 上帝 store(③)、settings 死代码(②)三处结构性债。
务实画像契合度:整体偏「做减法、反过度抽象」——没为插件化搞动态加载、没给 df-core 塞虚 trait、Registry 用朴素 HashMap。违背处集中在 df-execute 存在(应删)和 src-tauri 膨胀(编排无处去)+ 前端 AI 模块拆分。
行动建议(按 ROI):
| 优先级 | 项 | 动作 | 成本 |
|---|---|---|---|
| 立即 | ② | 删 stores/settings.ts 死代码 | 零风险 |
| 立即 | ⑤ | /decisions 死链(补路由 or 删 nav) | 零风险 |
| 短期 | ① | df-execute 合并进 df-nodes + 删死依赖 | 低(1 crate+1 依赖) |
| 短期 | ⑪ | stores/index.ts 补 useAiStore 导出 | 极低 |
| 中期 | ③ | project.ts 拆分 + 审批沉 api 层 | 中 |
| 中期 | ④ | ai composables 合回 or 破环 | 中 |
| B 路线 | ⑧⑨⑭ | IPC 编排层抽取 / agent loop 下沉 / AiSession 多会话 | 大(随 B 路线) |