文档: 架构方案文档(意图识别论证+多主题愿景/论证+文档物理分类+边界清晰化)

squash合并:
- 意图识别层论证(8维度+10业界佐证)
- 多主题上下文管理愿景+并存论证+补充论证(多轮agentic)
- 架构设计文档物理分类(四子目录+INDEX+命名规范+引用同步+边界清晰化)
- 前端架构技术债清单归档
This commit is contained in:
2026-06-19 15:04:04 +08:00
parent f1a06732fd
commit 998a2f243d
73 changed files with 1083 additions and 80 deletions

View File

@@ -0,0 +1,48 @@
# Phase 1 架构决策
> 创建: 2026-06-10 | 状态: 初稿
---
## 概述
Phase 1 是 DevFlow 的引擎骨架阶段,聚焦于核心数据流打通。本文记录此阶段的关键架构决策。
## 决策记录
### ADR-001: 引擎不绑定业务
- **决策**: Workflow Engine (df-workflow) 只做 DAG 执行,不感知具体业务语义
- **原因**: 保持引擎通用性
- **影响**: df-workflow 的 Node trait 是纯接口,业务逻辑在 df-nodes 实现
> ⚠️ 原文写"阶段逻辑通过 df-stages 插件化注入"、"业务逻辑在 df-nodes / df-stages 实现"。`df-stages` crate 已删除(零引用清理),实际无 stages 层。业务逻辑直接在 df-nodes 的 3 个节点实现。
### ADR-002: 本地优先架构
- **决策**: 使用 SQLite 嵌入式数据库,不依赖云服务
- **原因**: DevFlow 定位为桌面工具,零运维,离线可用
- **影响**: 无网络层、无认证系统,数据全部本地存储
### ADR-003: 多 Crate Workspace
- **决策**: 拆分为多个独立 crate
- **原因**: 模块解耦、独立编译、按需引用
- **影响**: 依赖关系需严格管控,避免循环依赖
> ⚠️ 原文写"13 个独立 crate",属过时数字(原始设计值)。实际为 **8 个 crate**df-core / df-workflow / df-nodes / df-ai / df-execute / df-storage / df-ideas / df-project。详见 [业务系统设计](./业务系统设计-2026-06-12.md) §六。
### ADR-004: 无 panic 原则
- **决策**: 所有占位代码返回空/默认值,不使用 `todo!`/`unimplemented!`
- **原因**: 保证应用不会因为未实现功能而崩溃
- **影响**: 未实现的方法返回 `Ok(default)` 而非 panic
### ADR-005: Phase 1 最小可用路径
- **决策**: 优先打通 `df-core → df-workflow → df-storage → Tauri IPC → Vue` 链路
- **原因**: 验证架构可行性,尽早发现集成问题
- **影响**: Phase 1 不实现 AI、想法池、多项目等高级功能
## 参考文档
- `ARCHITECTURE.md` — 完整架构设计
- `PROGRESS.md` — 当前进度与全局性问题

View File

@@ -0,0 +1,282 @@
# DevFlow 业务系统设计
> 创建: 2026-06-10 | 状态: 设计中 | 最后重写: 2026-06-15 (DOC-01 硬伤修复)
>
> **本文档为 ARCHITECTURE.md 的实质载体**(项目无独立 ARCHITECTURE.md 文件)。所有数据模型、设计决策均以源码为基准,已剔除虚构内容。
---
## 一、产品定位
| 维度 | 定义 |
|------|------|
| **一句话** | AI 原生的个人开发流程驾驶舱,从想法到任务到工作流的本地工具 |
| **目标用户** | 个人开发者 |
| **核心价值** | 想法池 → 项目 → 任务 → 工作流DAGAI 贯穿每个环节 |
| **差异化** | 想法第一公民 + AI 全程参与 + 本地优先(零运维) |
---
## 二、用户旅程设计
### 2.1 核心旅程
```
想法池 项目 任务 工作流
────────────────────────────────────────────────────────────────────────────
捕捉想法 → 评估评分 → 晋升立项 → 创建任务 → 绑定分支 → 执行 DAG 工作流
│ │ │ │
└── 淘汰/归档 └── 多任务 └── 自动执行 └── Script/Ai/Human
```
### 2.2 四个阶段详细设计
#### 阶段一:想法池 (Idea Pool)
**用户场景**:快速捕捉想法、评估、筛选。
| 操作 | 描述 | AI 参与 |
|------|------|---------|
| **捕捉** | 文本输入 | 无 |
| **评估** | 启发式评分(可行性/影响力/紧迫性) | 当前固定算法Phase 2 接 LLM |
| **晋升** | 高分想法晋升为项目 | AI 生成项目初始化建议 |
| **淘汰** | 低分想法归档或删除 | 无 |
**状态机**(对齐 `IdeaStatus` 枚举):
```
draft → pending_review → approved → promoted正向
→ rejected → archived淘汰
```
#### 阶段二:项目 (Project)
| 操作 | 描述 | AI 参与 |
|------|------|---------|
| **创建** | 从想法晋升 或 手动创建 | AI 生成描述/技术栈建议 |
| **绑定目录** | 关联本地代码目录(自动探测技术栈) | 无 |
| **软删/恢复** | 回收站机制deleted_at | 无 |
**状态机**(对齐 `ProjectStatus` 枚举):
```
planning → in_progress → testing → releasing → completed
→ paused → in_progress恢复
→ cancelled
```
#### 阶段三:任务 (Task)
| 操作 | 描述 | AI 参与 |
|------|------|---------|
| **创建任务** | 标题+描述 | AI 从需求拆解任务 |
| **执行工作流** | 触发 DAG 工作流 | AI 参与每个 Ai 节点 |
**状态机**(对齐 `TaskStatus` 枚举7 态):
```
todo → in_progress → in_review → testing → done
→ blocked → in_progress解除阻塞
→ cancelled
```
#### 阶段四:工作流 (Workflow)
**实际内置 3 种节点类型**(均在 `crates/df-nodes/src/` 完整实现):
| 节点 | 文件 | 作用 | 阻塞 |
|------|------|------|------|
| **Script** | `script_node.rs` | Shell 命令执行(经 `df-execute::shell` | 否 |
| **Ai** | `ai_node.rs` | LLM 文本生成/分析(非流式 complete | 否 |
| **Human** | `human_node.rs` | 人工审批/确认(单选/多选) | 是 |
> 不存在 Condition / Parallel / Docker / Git / Notify / HTTP / Subflow 节点。
> 条件分支由工作流引擎层处理(条件表达式引擎见 `条件表达式引擎-2026-06-15.md`)。
**工作流执行生命周期**
```
pending → running → completed
→ paused → running恢复
→ failed → running重试
→ cancelled
```
---
## 三、跨领域功能设计(已实现)
### 3.1 知识库 (Knowledge)
Tier1 AI 提炼:从 AI 对话中自动提炼候选经验条目,附带 reasoning 判断依据。
| 知识类型 | 来源 | 复用场景 |
|---------|------|---------|
| 审查规则 | 代码审查结论 | 后续审查参考 |
| Prompt 模板 | 成功的 AI 对话 | 类似场景复用 |
| 踩坑经验 | 错误修复过程 | 类似问题提醒 |
**生命线**candidate → pending_review → published → archived带 reuse_count / verified 信号。
### 3.2 AI 多 Provider
支持配置多个 AI 提供商OpenAI 兼容 / GLM / DeepSeek / Anthropic 原生协议),可在设置中管理并指定默认。
详见 [df-ai AI集成模块](../03-模块文档/df-ai-AI集成模块-2026-06-12.md)。
### 3.3 EventBus 事件总线
进程内 `tokio::sync::broadcast` 发布/订阅,前端经 `@tauri-apps/api/event` 的 emit/listen 接收。**不是 WebSocket**。
---
## ~~三、跨领域功能设计(已废弃规划)~~
> 以下章节曾详述标注系统、决策留痕、经验进化、AI 编排ModelRouter/Agent 协作)等设计。
> 这些功能**从未实现**对应表annotations/decisions/features/test_cases也从未建表。
> 保留此节仅作历史存档参考,读者应视为"规划意图"而非"现有能力"。
### ~~3.1 标注系统 (Annotation)~~ — ❌ 未实现
### ~~3.2 决策留痕 (Decision Journal)~~ — ❌ 未实现
### ~~3.3 经验进化 (Evolution)~~ — ⚠️ 部分落地为知识库knowledges 表),但远不及原规划规模
### ~~3.4 AI 编排ModelRouter / Agent 协作)~~ — ❌ ModelRouter 从未存在Agent 协作属 Phase 2 规划B 路线)
---
## 四、数据模型设计V1-V13 迁移实际表)
> 核对基准:`crates/df-storage/src/migrations.rs` 建表 SQL + `models.rs` Record 结构体。
### 全量表清单13 业务表 + 1 元表)
#### 活跃业务表11 张)— 有上层代码读写
| # | 表名 | 建表版本 | 用途 | 对应 Model | 活跃消费者 |
|---|------|---------|------|-----------|-----------|
| 1 | `ideas` | V1+V2 | 想法池 | IdeaRecord | df-ideas crate |
| 2 | `projects` | V1+V11+V12 | 项目管理 | ProjectRecord | df-project crate |
| 3 | `tasks` | V1+V2 | 任务管理 | TaskRecord | commands::taskIPC handler 直连 CRUD |
| 4 | `workflow_executions` | V1+V2 | 工作流执行实例 | WorkflowRecord | df-workflow crate |
| 5 | `node_executions` | V1 | 节点执行审计 | NodeExecutionRecord | df-workflow executor |
| 6 | `ai_conversations` | V3+V4/V5/V6 | AI 对话历史 | AiConversationRecord | commands::ai |
| 7 | `ai_providers` | V9 | AI 提供商配置 | AiProviderRecord | commands::ai::provider |
| 8 | `ai_tool_executions` | V9 | AI 工具调用审计 | AiToolExecutionRecord | commands::ai |
| 9 | `knowledges` | V7+V8/V10 | 知识库条目 | KnowledgeRecord | commands::knowledge |
| 10 | `knowledge_events` | V10 | 知识生命线事件 | KnowledgeEventRecord | commands::knowledge |
| 11 | `app_settings` | V13 | 通用 KV 设置 | (无独立 model) | commands::settings手写 Repo |
#### 遗留表2 张)— DDL 存在但无活跃业务消费者
> `df-task` crate 已于 2026-06-14 移除(零引用清理)。以下表仍在 migrations.rs 中创建、models.rs 有结构体、CRUD 可用,但当前**无上层业务代码写入或消费**。
| # | 表名 | 建表版本 | 原始用途 | 状态 |
|---|------|---------|---------|------|
| 12 | `branches` | V2 | Git 分支绑定 | ⚠️ 无消费者DDL 存在CRUD 可用但无人调用) |
| 13 | `releases` | V1 | 发布记录 | ⚠️ **功能性死表**DDL 存在且含 version/status/task_ids/changelog/released_at 完整 schema但全代码库零业务读写——无 ReleaseStatus 枚举、无 release 相关 IPC command、前端无发布管理页面。属"建了但从未使用"的空壳占位。 |
#### 内部元表
| # | 表名 | 建表版本 | 用途 |
|---|------|---------|------|
| - | `schema_version` | V0 | 迁移版本跟踪(仅存 version INTEGER无业务语义 |
### 不存在的表(曾出现在早期规划但从未建表)
| 表名 | 状态 | 说明 |
|------|------|------|
| `workflow_defs` | ❌ 从未建表 | 工作流定义以 dag_json 内嵌在 workflow_executions 中 |
| `connections` | ❌ 从未建表 | 连接配置使用 app_settings KV 表存储 |
| `artifacts` | ❌ 从未建表 | 产出物概念未落地 |
| `annotations` | ❌ 从未建表 | 标注系统属已废弃规划 |
| `decisions` | ❌ 从未建表 | 决策留痕属已废弃规划 |
| `features` | ❌ 从未建表 | 需求功能清单未落地 |
| `test_cases` / `test_runs` | ❌ 从未建表 | 测试模块未落地 |
| `knowledge`(单数)| ❌ 不存在的旧命名 | 实际表名为 `knowledges`复数V7 建表 |
| `merge_requests` | ❌ 从未建表 | 合并请求未落地 |
---
## 五、关键设计决策
### D1: 想法是第一公民
- 想法池独立于项目,可以独立运转
- 晋升是单向操作(想法→项目),但保留追溯
### D2: 本地优先
- SQLite 嵌入,不依赖云服务
- 所有数据存储在本地
- 零运维,安装即用
### D3: 引擎不绑定业务
- DAG 引擎纯粹做编排,不感知具体业务语义
- 业务逻辑在 df-nodes 实现Node trait 是纯接口)
### D4: AI 贯穿全程
- AI Chat 对话 + 工作流 AiNode 双路径
- AI 输出作为决策依据,最终决策权在人
---
## 六、Crate 结构
实际 **8 个 crate**`crates/` 目录下):
| Crate | 职责 |
|-------|------|
| `df-core` | 公共类型types.rs、事件定义、工具函数 |
| `df-workflow` | DAG 引擎拓扑排序、执行器、Node trait |
| `df-nodes` | 内置节点Ai / Script / Human |
| `df-ai` | AI 集成层LlmProvider trait、OpenAI 兼容、Anthropic、ContextManager、工具注册基础设施 |
| `df-execute` | Shell 执行(跨平台封装) |
| `df-storage` | SQLite 存储层migrations、CRUD 宏、Repo |
| `df-ideas` | 想法池业务逻辑(评估、晋升) |
| `df-project` | 项目管理业务逻辑(目录绑定、技术栈探测) |
> 原始设计文档Phase1架构决策 ADR-003曾写 "13 个独立 crate",属过时数字,未随代码演进更新。实际为以上 8 个。
---
## 七、MVP 验证场景
**Phase 1 目标**:跑通"创建想法 → 晋升项目 → 创建任务 → 执行 3 节点工作流 → 查看结果"
```
1. 用户在想法池输入"做一个 Markdown 编辑器"
2. 启发式评估可行性,给出评分和建议
3. 用户点击"晋升为项目"
4. 系统创建项目
5. 用户创建任务"实现基础编辑功能"
6. 用户点击"运行工作流"
7. DAG 执行: [Script: 环境检查] → [Ai: 代码生成] → [Human: 审批]
8. 前端经 EventBus 实时展示执行日志
9. 执行完成,结果持久化到 SQLite
10. 用户刷新页面,数据仍在
```
---
## 八、已确认的设计决策
### Q1: 想法评分维度 ✅ 已确认
**决策**:采用 C 方案 — 可行性/影响力/紧迫性 + 综合分 (3+1 维)
-`evaluator.rs` 已实现的 `EvalDimension` 对齐
- `IdeaScores { feasibility, impact, urgency, overall }` 保留
### Q2: 发布模块 ✅ 已确认(当前为死表状态)
**决策**Phase 1 不做发布功能。releases 表 DDL 存在但无业务逻辑,待后续激活。
- 不做自动化发布流程
- 前端无发布入口
### Q3: AI 评估 Phase 1 范围 ✅ 已确认
**决策**Phase 1 用固定算法评分,延后接入 LLM
- `ScoringEngine` 当前返回基于启发式规则的分数
- Phase 2 接入 LLM 后替换为 AI 评分
---
## 相关文档
- [df-nodes 节点集合](../03-模块文档/df-nodes-节点集合-2026-06-12.md) — 3 节点详述
- [df-ai AI 集成模块](../03-模块文档/df-ai-AI集成模块-2026-06-12.md) — Provider / Context / 工具注册
- [df-storage 存储层](../03-模块文档/df-storage-存储层-2026-06-12.md) — 迁移 / CRUD / Repo
- [df-workflow 工作流引擎](../03-模块文档/df-workflow-工作流引擎-2026-06-12.md) — DAG / Executor
- [Phase1 架构决策](./Phase1架构决策-2026-06-12.md) — ADR 记录注意ADR-001/003 含过时信息,以本文档为准)

View File

@@ -0,0 +1,568 @@
# 功能决策记录
> 日常开发中对各功能做的**需求规格 + 设计决策规格**(功能粒度,补 [Phase 1 架构决策](./Phase1架构决策-2026-06-12.md) 之下的实现层选择)。聚焦「要做什么 / 为什么这么定」,便于日后回溯。
>
> 创建2026-06-12 | 范围Sprint 510 | 维护:随开发追加
## 约定
**判断标准**3 个月后回看,这条是否仍影响对系统/功能设计的理解?是 → 留本文档;否 → 分流。
**本文档只记两类**
- **设计决策规格**(✅ 已落地 / 🚧 待实测 / 📐 设计未实施)——「为什么这么定」。三要素:决策 → 原因/取舍 → 状态。来源标 `[Sprint N]``[日期]`
- **需求规格 / 待办**(📋)——「要做什么 / 为什么需要」。与 PROGRESS 流水区分:这里记「要做什么 / 为什么需要」PROGRESS 记「做了啥」。
**经验性内容**(踩坑 / 约定 / 技巧 / bug 排查教训)→ [经验记录-2026-06-14.md](./经验记录-2026-06-14.md)。
**老条 / 纯流水 / UX 微调 / 已被取代** → [功能决策记录-归档-2026-06-14.md](./功能决策记录-归档-2026-06-14.md)。
记录规则见 [文档记录规范](./文档记录规范-2026-06-14.md)。
## 人机协同设计基准
### AI coding 下的「过度设计」判断基准 [2026-06-13]
- **决策**:项目为一人开发 + 全程 AI coding人定方向/审查AI 实现),代码被 AI 反复读写。权衡设计时采用新基准——**AI 反复读写的代码,「结构清晰」和「隐性耦合显式化」权重高于传统判断**;但「过度抽象」(多层 trait/Builder/工厂)仍不做,因 AI 读简单直白代码 > 读层层抽象。
- **原因/取舍**:① AI 每次理解代码靠注释和结构比人更依赖1600 行单文件(如 AiChat.vue消耗大量 context拆分反而降低 AI 理解成本;② 隐性依赖(如分离窗口 localStorage 跨 webview人凭经验避开、AI 易踩坑。故:组件拆分从「不做」升为「值得做」;隐性耦合早修或显式标注。③ 规模没到的优化list_all LIMIT、白名单拆表仍按数据量客观判断不因 AI coding 而变。
- **状态**:📐 基准原则(指导后续取舍)
## AI Chat 可靠性
### 流式可靠性三重保险 [Sprint 6 + 2026-06-13]
- **决策**:① reqwest Client 加 `connect_timeout(30s)`**不设请求总 timeout**;② `stream_llm``tokio::time::timeout(120s)` 包**单个** `stream.next()`idle 间隔),不包整个流;③ 前端 stores/ai.ts 加 streaming watchdog——发送启动 60s 计时,收到 AiTextDelta/工具事件/审批结果重置AiApprovalRequired 暂停审批等待不计AiCompleted/AiError 清除60s 无活跃事件则置 streaming=false + push 错误「响应中断」。
- **原因/取舍**:连接阶段防无限 hang总 timeout 会误砍流式长生成任务流式可持续数分钟。idle 120s 防「连上后中途静默」无限 hang只卡单 chunk 间隔,不卡总时长。前端 60s 独立计时(短于后端 120s双保险——后端任何路径漏发收尾崩溃/事件丢失/agent loop 异常退出)前端永久 streaming=true 卡死。审批等待暂停(不计超时)避免误判用户思考。
- **边界**审批组件未渲染见「需求与待办·审批可见性缺口」AiApprovalRequired 暂停后永久卡watchdog 救不了,需审批可见性兜底。
- **状态**:✅ Sprint 6 + 2026-06-13
### 断连丢弃残缺响应
- **决策**:维护 `finished_received` 标志;流尽未收到 finished 信号 → emit AiError 并**丢弃残缺响应,不当完整入库**。
- **原因**:防脏历史污染对话记录(半截回复入库后无法续接)。
- **状态**:✅ Sprint 6
### 停止生成:保留已生成文本
- **决策**`AiSession.stop_flag: Arc<AtomicBool>` + 多检查点响应;停止后**保留已生成文本**。
- **原因**:用户主动停止 ≠ 丢弃成果已输出内容有价值。idle无输出时最多等 120sP2 可用 `tokio::sync::Notify` 优化为绝对即时)。
- **状态**:✅ Sprint 6运行时待实测
### 切对话:从「拒绝切换」演进到「不中断路由」
- **决策**Sprint 6 生成中**拒绝切换**(防 `active_conversation_id` 被改致旧 loop 串台写库)→ Sprint 8 改为**后台对话按 `conversation_id` 路由,生成中可切换不打断**。
- **原因**:拒绝切换体验差;后端给所有 event 加 `conversation_id` + spawn 前快照 conv_id + 前端按 id 路由(后台对话事件不污染当前视图),既不串台又不打断。
- **状态**:✅ Sprint 8部分场景待实测
## AI Chat 工具调用与审批
### Agentic Loop 最多 10 轮
- **决策**`run_agentic_loop` 上限 10 轮(`MAX_AGENT_ITERATIONS`)。
- **原因**:防失控循环;单链 ReAct 10 轮覆盖绝大多数任务。超出需规划式B 路线)。
- **状态**:✅ Sprint 5
### 风险门控Low 自动 / Medium+High 审批
- **决策**:工具按 `RiskLevel` 分级Low 自动执行Medium/High 暂停等人工审批。
- **原因**:读操作放行,写/删操作把关——可靠性 vs 效率的平衡点。
- **状态**:✅ Sprint 5
### `tool_calls` 按 index 排序
- **决策**assistant 消息与 tool_result 两处均按 `index` 排序。
- **原因**:消除 HashMap 迭代乱序致多工具结果错位。
- **状态**:✅ Sprint 6
### 路径校验:拒 `..` 遍历 + 扩敏感目录
- **决策**:正斜杠→反斜杠规范化 + 拒 `..` 路径遍历 + 扩 `.aws`/`.gnupg`
- **原因**最小加固防越权读写根治级workspace 白名单 + canonicalize待边界明确后再做。
- **状态**:✅ Sprint 6边界加固待续
### list_directory 递归防爆:噪音目录剪枝 + 条目上限 + skip_noise_dirs 开关 [2026-06-14]
- **决策**`list_dir_recursive` 递归时跳过噪音目录(`.git`/`node_modules`/`target`/`dist`/`build`/`.next`/`.cache`/`__pycache__`/`.venv`/`venv`/`.idea`)——**列出但不深入内部**;硬上限 1000 条 + `truncated` 标志;默认 `max_depth` 3→2`skip_noise_dirs` 参数(默认 `true`)。
- **原因**AI 广扫项目根传 `recursive:true` 时,`.git`/`node_modules`/`target` 铺平致 13782 项塞进对话 messageUI 卡 + token 爆)。剪枝防爆炸,但保留访问能力:① 噪音目录仍列出(看得见存在 + 大小);② 想看内部时 `list_directory` 直接指向该目录depth=0 起算),或传 `skip_noise_dirs:false` 强制递归进去(仍受 1000 上限 + truncated 保护,适合看编译产物 dist / 运行结果 target 做比对。1000 上限 + truncated 让"想全扫"退化为"分层定点查",不丢信息。
- **状态**:✅ 2026-06-14 落地cargo check 通过)
### `max_tokens` 8192 + `length` 算 finished
- **决策**max_tokens 4096→8192`finish_reason="length"`(截断)纳入 finished。
- **原因**:大任务输出撞 4096 上限被误判断连、丢弃整段响应8192 贴合实际,截断视为正常完成。
- **状态**:✅ Sprint 6
### 审批:删全屏 Modal 保留行内卡片 [Sprint 8]
- **决策**:移除全屏 Tool Approval Modal保留工具卡片内联审批按钮。
- **原因**:全屏 Modal 打断对话流,行内审批更轻量。
- **状态**:✅ Sprint 8
## AI Chat Provider 协议
### 按 `provider_type` 路由 OpenAI / Anthropic
- **决策**:新增 `anthropic_compat.rs` 实现 Anthropic Messages API`provider_type` 分发到 OpenAICompat 或 AnthropicCompat分发处用 `Box<dyn LlmProvider>` trait object。
- **原因**GLM 等订阅端点走 Anthropic 协议(`x-api-key` + 顶层 `system` + 必填 `max_tokens` + SSE content_block统一 Provider trait 屏蔽差异,上层 Agentic Loop / AiNode 零改动(不感知协议)。
- **状态**:✅ Sprint 8用户实测对话流式 OK
### 端点 URL 三段智能拼接
- **决策**`messages_url()` 按 base_url 末段判断——已含 `/v1/messages` 直用;以 `/v1` 结尾补 `/messages`;仅域名(如 `…/api/anthropic``api.anthropic.com`)补 `/v1/messages`
- **原因**GLM 订阅端点 `open.bigmodel.cn/api/anthropic` 与 Claude 官方 `api.anthropic.com` 约定不同(前者无 `/v1`后者需补不强制用户填全路径降低配置门槛。GLM 端点已实测。
- **状态**:✅ Sprint 8
### `max_tokens` 必填兜底 4096 / tool_result 连续合并为一条 user
- **决策**:① Anthropic 协议 `max_tokens` 必填(协议无默认),`DEFAULT_MAX_TOKENS=4096` 兜底OpenAI 协议 max_tokens 可选,缺则报错);② 连续多条 `role=Tool`tool_result累积遇非 Tool 消息 flush 为单条 user 消息含多个 `tool_result` 块。
- **原因**:① 统一兜底避免上层每个调用点都要传值(与 OpenAI Provider 的 8192 上限独立,此处仅缺省兜底)。② Anthropic 要求 tool_result 必须在 user 角色内;多工具并发结果合并为一条 user 而非一对一,贴合协议「一回合一组结果」语义,减少消息碎片。
- **状态**:✅ Sprint 8
### 默认标识is_default 落库为真相源 [Sprint 10 → 2026-06-13]
- **决策**`ai_set_provider` 互斥写 DB目标 `is_default=true`、其余 `false`,仅写变化记录);`ai_save_provider` 新建时若全表尚无默认则自动设为默认(首个);`ai_list_providers` 直接返 DB 值。`session.active_provider_id` 降为运行时缓存,由 `set_provider` 同步,重启清零不影响——`get_active_provider` 兜底取 DB `is_default`
- **原因/取舍**Sprint 10 原 active 作真相源 → 致「重启默认丢失」bugactive 是内存态重启清零,而 `is_default` 字段恒写 false重启后无默认可恢复。`is_default` 字段本为持久化默认而存在回归本职最自然session 持久化需额外存储,重复造轮子。互斥写库保证「唯一默认」语义。
- **边界**:互斥写库逐条 `update_full` **不加事务**——repo 未暴露事务接口;桌面单用户无并发触发,失败即报错、重试自愈。多用户/高并发场景需给 repo 补 `execute_transaction`
- **状态**:✅ 2026-06-13 落地(重启保默认 / 互斥写库 / 首个自动默认实测待补)
### 📋 delete_provider IPC 缺失(前端假删除)[Sprint 10]
- **需求**Settings 删 Provider 当前只前端 filter 移除,不调后端(无 `ai_provider_delete` 命令),重启后配置回归。
- **原因**交互欺骗——用户以为删除成功DB 实际未动。需补 `ai_provider_delete` IPC`lib.rs` 注册 + `ai.rs` 实现 + 前端真调),并加二次确认。
- **状态**:📐 待实施 → ✅ Sprint 10 已实现(`ai_delete_provider` 命令 + 删默认清 active + 自建 `confirmDialog` 二次确认)
### 多 Provider 负载均衡池ProviderPool 归 app 层 + 模型亲和排序 + fallback 分类 [2026-06-17]
- **决策**ProviderPool 实现select/fallback/capacity放在 `src-tauri/commands/ai/` 目录下(非 df-ai crate`prompt.rs::get_active_provider` / `secret.rs::build_provider_for` 同属「消费 AiProviderRecord 的 app 层」。select 排序规则:模型亲和(含 model_id 的 provider 排前)> weight 降序 > is_default 兜底。fallback 策略InitFailedretryable耗尽候选后切下一个 providerFatal4xx 非 429立即放弃整个 fallback 链。per-provider 并发 cap = global_cap差异化 cap 留后续)。否决健康度路由(需持久化健康状态,过度工程;瞬态故障由 fallback 吸收)和纯轮询(加权是轮询超集)。
- **原因/取舍**
- **位置选 commands/ai/ 非 df-ai**df-ai 定位是「协议适配 + Provider trait」存储无关引入 ProviderPool 会创建 df-ai→df-storage 依赖(读 AiProviderRecord破坏存储无关边界。commands/ai/ 本就是 app 层装配点get_active_provider / build_provider_for 都在此层ProviderPool 放此处语义一致。
- **模型亲和排第一**F-01 智能路由已按任务需求选定模型+provider 组合,若 select 排序不含模型亲和可能换到不含该模型的 provider导致路由结果失效。模型亲和保「路由选的模型一定在选中 provider 上可用」。
- **否决健康度路由**:健康检查需持久化状态(上次成功时间/连续失败计数),桌面单用户场景 provider 数量少(通常 2-5 个),瞬态故障由 fallback 重试吸收即可。健康度增加的复杂度(定时探测/状态序列化/启动恢复)远大于收益。
- **Fatal 立即放弃**:对齐 retry.rs 的 Fatal 分类4xx 非 429 = 请求本身非法,重试无意义),避免无效重试浪费 quota 和延迟。
- **状态**:✅ 已落地commit 79b6a43 / b3684f4 / 80c0955
## 模型能力与路由Model Capability & Auto-Routing
### 能力声明:复用 `ai_providers.models` JSON 字段,不建新表 [2026-06-13]
- **决策**:每个 Provider 下可选模型的能力声明(模态/功能/成本等级)存入已有 `ai_providers.models`JSON 数组),**不新建独立表**。新增 `crates/df-ai/src/model_capability.rs` 定义 `ModelCapability`name / modalities / functions / max_tokens / cost_tier+ `TaskRequirements` + `Modality` / `CostTier` 枚举。
- **原因/取舍**:模型能力是 Provider 配置的内在属性,非独立实体——无生命周期管理、无跨表 JOIN 需求JSON 嵌入单行足够Provider 通常 1-5 个);`models` 列已在 V9 建表且全链路预留,复用零 schema 变更;独立表需外键/级联/JOIN对桌面应用过度工程备份迁移友好配置自包含一行。代价无法 SQL 查询「所有有 vision 的模型」,但此查询当前和近期均不需要。
- **状态**:📐 设计未实施Phase 1数据模型 + 场景级路由)
### 路由层:纯函数 ModelRouter重写现有骨架 [2026-06-13]
- **决策**:重写 `crates/df-ai/src/router.rs` 已有但空的 `ModelRouter`——从 `TaskRequirements` + Provider model_pool → 按硬性要求筛选候选 → 按 cost_tier 升序取最便宜。路由是同步纯函数(无 I/O、无全局可变态可单测。7 个 LLM 调用点统一接入。`override_model` 字段支持用户显式指定(聊天手动切)和工作流节点 config.model节点作者指定两种覆盖优先级最高。
- **原因**:路由是确定性计算(静态配置→模型名),无需 async/service 化;纯函数可单测;统一入口避免散装 if-else。
- **各调用点路由策略**:主对话 `chat(has_image)`→Standard/Premium标题生成 `title_generation()`→Economy 最便宜;知识提炼 `knowledge_extraction()`→Economy/Standard工作流 AiNode `workflow_node()` + override=config.model→节点指定优先**Embedding (×2) 不走路由器**(独立路径,不同模型类)。
- **向后兼容**`models=None`(老记录)→ model_pool 空 → 所有 route 返回 default_model → 行为不变。
- **状态**:📐 设计未实施Phase 1
### Embedding 不进通用路由器 [2026-06-13]
- **决策**Embedding 模型不走 ModelRouter继续走独立路径`KnowledgeConfig.embedding_model` + `embedding_provider_id`)。
- **原因**Embedding 是完全不同的模型类——API 不同(`/v1/embeddings` vs `/v1/chat/completions`)、用途不同(向量化 vs 生成)、通常更小更专用。混入通用模型池会混淆用户并增加路由分支复杂度。
- **状态**:📐 设计未实施Phase 1 确认不动 embedding 路径)
### 分阶段实施路线 [2026-06-13]
- **Phase 1**(本次):数据模型 + ModelRouter 重写 + 7 调用点接入 + Settings 模型池编辑 UI + AiChat 模型下拉。ChatMessage.content 保持 String 不改。
- **Phase 2**后续多模态消息——ChatMessage.content: String → Vec<ContentPart>(Text/Image);前端粘贴/拖拽图片vision 模型自动路由。
- **Phase 3**后续Agent 内智能路由——Agentic Loop 每轮按子任务构造不同 TaskRequirements成本预算控制模型级联降级跨 Provider 搜索。
- **状态**:📐 设计未实施
### 📋 模型能力系统 — 完整改动文件清单 [2026-06-13]
- **后端 Rust**`crates/df-ai/src/model_capability.rs`**新增** ModelCapability / TaskRequirements / Modality / CostTier/ `router.rs`**重写** ModelRouter 匹配逻辑)/ `lib.rs`re-export/ `df-storage/src/migrations.rs`V10 版本号推进,无需 ALTER/ `src-tauri/src/commands/ai.rs`7 调用点加 routerai_save_provider 加 models 参数;新增 ai_set_chat_model_override
- **前端 TS/Vue**`src/api/types.ts`(新增 ModelCapability / Modality / FunctionCapabilities / CostTier 类型)/ `src/api/ai.ts`saveProvider 加 models 参数;新增 setChatModelOverride()/ `src/stores/ai.ts`availableModels / activeModelOverride 状态 + setModelOverride action/ `src/views/Settings.vue`Provider 表单增加模型池编辑区)
- **状态**:📐 待实施Phase 1 全量清单)
### 多模态消息content:String 保留 + parts 新增,务实偏离 Vec<ContentPart> 原案 [2026-06-17]
- **决策**Phase 2 多模态原设计 `content: Vec<ContentPart>` 改为 **`content: String` 保留不变 + 新增 `parts: Option<Vec<ContentPart>>`**。老 JSON 反序列化时 parts=None 零回归。后续若严格对齐 content:Vec 需先解禁 4 个 forbidden 文件的 content 消费点(改用 content_text() / flattened_parts() 辅助方法)。
- **原因/取舍**
- **4 个 forbidden 文件直接消费 m.content:String**——`audit.rs`(审计日志读 content/ `title.rs:50`(结构体字面量 ChatMessage{ content: ..., role: ... }/ `commands.rs`IPC 层序列化)/ `knowledge_inject.rs`(知识注入读取)。其中 title.rs 是结构体字面量构造Rust 不支持字段默认值,加任何 required 字段必炸编译。
- **不改 content 为 Vec 的代价可控**——parts 携带多模态数据content 保留纯文本降级路径。前端/LLM 层按 parts 是否 Some 判断是否多模态,老路径不受影响。
- **向后兼容**——serde `#[serde(default)]` 让缺失 parts 字段的老 JSON 反序列化为 None零回归风险。
- **状态**:✅ 已落地commit e3cd448
## 灵感模块(评估闭环)
### 启发式评分维度
- **决策**:三维固定 5.0 → 内容启发式priority / 描述充实度 / tags / 中英关键词clamp 0-10。
- **原因**:固定值无区分度;启发式基于 idea 内容给差异化评分。
- **状态**:✅ Sprint 9启发式未接 LLM
### 对抗评估:启发式 fallback 待接 LLM
- **决策**:正反方论点/evidence 基于真实 idea 内容生成confidence 由评分驱动;未接 df-ai LlmProvider。UI 诚实标注——评估区标题加「启发式」黄标(`.eval-mode-tag`),对齐实现深度避免名实不符,接 LLM 后摘除。
- **原因**先打通评估闭环LLM 生成论点 + 启发式 fallback 为后续增强。
- **→ 接入方式已定**:走全局 AI trait 下沉(见 [crate 治理 — AI trait 下沉拆 df-ai-core](#ai-trait-下沉拆-df-ai-core-轻层确立全局-ai-接入标准-2026-06-14-))。`evaluate()``LlmProvider` 注入参数,启发式降级为 fallbackF-03 原 A/B/C 选型据此收敛为「全局统一 trait」。
- **状态**:📐 设计未实施LLM 接入)
### 前后端标签对齐
- **决策**`recommendation` 全小写空格(后端原 "With Resources" 不匹配前端 map key`assessmentClass` 映射到 CSS 类名(`.immediate`/`.soon`/...)。
- **原因**:大小写/类名不一致致中文标签不显示、badge 无色。
- **状态**:✅ Sprint 9
### 评分 0-10crate→ 0-100前端IPC 缩放 + 多字段写回用单事务 [Sprint 9/10]
- **决策**:① crate 内 IdeaScores 维持 0-10IPC 层 evaluate_idea 组装 scores JSON 时 *10 缩放为 0-100并用中文维度键可行性/影响力/紧急度/综合)。② evaluate_idea / promote_idea 写回想法用 `update_full`(单事务覆盖整条记录),放弃多次 `update_field`
- **原因**:① 前端雷达图直接当百分比渲染、零前端改动crate 内 0-10 符合评分直觉IPC 层做单位适配。② 多次 update_field 各自独立连接中途失败致数据半成品update_full 原子。
- **状态**:🚧 Sprint 9/10编译/构建通过,未 tauri dev 实测)
## 想法立项promotion
### 复用 df-project 领域层crate do_promote 留纯决策 TODO [Sprint 10]
- **决策**`promote_idea` IPC 复用 `df_project::manager::ProjectManager::create_from_idea` 构造项目实体 + 映射 ProjectRecord 持久化 + update_full 回写想法df-ideas crate 内 `IdeaPromoter/do_promote` 保留纯决策 TODO不真正创建项目。
- **原因**crate 不依赖 df-storage/df-project避免循环依赖、保持可单测手动立项无需 Auto/Manual/SemiAuto 策略判断(用户点即确认),副作用放 IPC 组合,与 evaluate_idea 同模式crate 纯决策留待自动/半自动晋升场景复用。
- **状态**:🚧 Sprint 10编译/构建通过,未 tauri dev 实测)
### promoted_to 非空拒绝重复立项 [Sprint 10]
- **决策**promote_idea 取想法后校验 promoted_to 已存在则返回错误,不创建新项目。
- **原因**:防同一想法多次点「立项」生成多个项目;幂等保护。
- **状态**:🚧 Sprint 10
## 项目管理(删除 / 回收站)
### 项目删除软删回收站deleted_at + 应用层级联),非物理删 [2026-06-13]
- **决策**:删项目改为**软删**——`projects``deleted_at TEXT`V11 迁移),`delete_project``deleted_at`(进回收站,可恢复);`list_projects` 过滤 `deleted_at IS NULL`;回收站(`list_deleted_projects`)可「恢复」(清 deleted_at或「彻底删除」`purge_project` 事务级联物理删 branches→releases→tasks→projects不可逆。子表软删时不动FK 仍满足,项目数据完整保留待恢复。
- **原因/取舍**:① 用户要求「可恢复」——纯 CASCADE 物理删不可逆,误删难挽回;软删 + 回收站给反悔余地。② SQLite `ALTER TABLE` 改不了已有表 FK 约束,给老库 projects 加 `ON DELETE CASCADE` 须重建表(高风险),故不走 DDL 级联,改**应用层级联**purge 时事务内顺序删子表),语义等价且可测、不依赖 `PRAGMA foreign_keys`。③ 软删只标记 projects 行、子表不动——恢复时项目连同历史任务/分支/发布完整还原。④ 两级风险分级:日常软删可逆 / 回收站 purge 二次确认后物理删不可逆。
- **边界**`ProjectRecord` 不带 `deleted_at` 字段,纯靠 SQL `WHERE deleted_at IS NULL` 过滤models/types 零变更。`ai.rs build_system_prompt` 同步改用 `list_active`(防软删项目泄漏进 AI 上下文)。`soft_delete`/`restore` 守卫对称,重复操作幂等。
- **状态**:✅ 2026-06-13 落地V11 迁移 + ProjectRepo 5 方法 + 3 IPC 命令 + 回收站 modal + 删除入口cargo check + vue-tsc + 11 integration test 全绿)
## 项目管理(目录绑定 / 技术栈探测)
### 项目绑定真实代码目录:扩 ProjectRecord + df-project scan [2026-06-13]
- **决策**`ProjectRecord``path`(绑定目录绝对路径)+ `stack`(技术栈 JSON 数组字符串两字段V12 迁移nullable技术栈探测逻辑放**新建 `df-project/src/scan.rs::detect_stack`**(纯函数,浅读根目录标志文件识别 rust/go/python/java/csharp/vue/react/angular/svelte/next/vite/typescript/node/tauricommands 层薄封装 4 命令(`scan_project_stack`/`check_path_binding`/`relocate_project_path`/`check_path_exists``check/relocate``canonicalize` 规范化路径防绕过重复检查。新建项目可选绑定目录→自动探测栈,详情页支持重定位 + 目录失联检测 + 防重复绑定。
- **原因/取舍**:① **扩 df-storage ProjectRecord 而非激活 df-project ProjectContext 空壳**——`ProjectContext` 虽早设计 `root_path`/`tech_stack`/`repo_url`/`ai_context` 字段,但 `manager.rs` 标注 TODO 从未接存储/运行时;激活需新建 `project_contexts` 表 + 填充暂不需要字段,第一步过重,守 YAGNI。ProjectRecord 是实际运行链路,直接扩最快见效。② **scan 放 df-project 而非 commands 层**——`ProjectContext.tech_stack` 本就是 df-project 职责字段scan 是其天然能力且可被 df-ai/df-workflow 复用,放 commands 变一次性代码。③ **migration nullable**——老项目 path/stack=NULL 零影响。④ **一致性原则**:先在「新建流」验证,第二步「导入历史项目」复用同一套 scan/relocate/checkBinding。
- **边界**`path` 规范化canonicalize仅用于比较存库保留用户输入的原始可读路径。程序化创建项目想法晋升、AI 工具path/stack = None不绑定目录`df-project``ProjectContext` 刻意未激活ai_context/repo_url 等暂留空)。
- **状态**:✅ 2026-06-13 落地V12 迁移 + detect_stack + 4 IPC 命令 + 选目录/查重/卡片栈 + 重定位/目录状态cargo build + scan 4 单测 + vue-tsc 全绿)。📋 导入历史项目(第二步):复用 scan/relocate/checkBinding加 monorepo 子目录识别 + README 首段抽 description + 批量。
### 导入/绑定项目 AI 扫描填信息:规则探测兜底 + LLM 增强,采样与 LLM 调用分层 [2026-06-14]
- **决策**:导入/绑定项目时规则 `detect_stack`(快/免费/准)必跑兜底 + LLM 分析采样README+目录树+清单,不读源码)产出 description 摘要与 stack 细化LLM 失败降级纯规则。采样逻辑放 df-project纯 IOLLM 调用放 commands 层。
- **原因/取舍**:① 规则兜底保证 LLM 不稳定时仍有基础信息LLM 只补规则搞不定的摘要。② 采样与 LLM 分层——采样纯 IO 属 df-project 职责可复用LLM 调用依赖 df-ai放 commands 使 df-project 保持无 LLM 依赖(防循环)。③ 不读源码控 token+隐私。④ 结果预览让用户把关防 LLM 瞎编。
- **状态**:✅ 2026-06-14 落地scan_project_with_ai 命令 + 前端 AI 扫描预览;编译/单测/类型全绿)。
### 导入历史项目scan 第二步设计description 走 LLM + 采样保留内容图 + monorepo 一层 + 批量并发 [2026-06-14]
- **决策**F-06 = scan 第二步,选根目录 → 发现项目(含 monorepo 子目录)→ 勾选批量导入。六点收敛:① **description 走 LLM** 复用 `scan_project_with_ai`command 层 complete**不做纯规则抽取**(跨 README 格式 brittle**采样改进**——`ProjectSample``images: Vec<ImageRef{alt,src}>``readme` 剥 frontmatter/TOC/纯徽章行后截 ~8KB`SAMPLE_README_MAX=2000` 偏小粗暴),**保留内容图 markdown 原样**;③ **image 多模态条件化**——当前 `ChatMessage.content:String`F-260614-05 未做)走纯文本降级,采样层先不丢 image 引用留接口Phase 2 上线后读 base64 喂 vision**monorepo 一层识别**`is_monorepo` 检 pnpm-workspace/lerna/turbo/nx + package.json workspaces`discover_projects` 展开 packages/\*/apps/\* 直接子目录,`detect_stack` 空的过滤);⑤ **批量流程**——`scan_directory_for_projects` 规则发现(快、不跑 LLM+ 标已绑定项;用户勾选后 `import_projects_batch` 对勾选项**并发** LLM 抽 description`llm_concurrency` 双层 permit 限流)+ 复用绑定入库,非原子逐项独立;⑥ **对称改进**——抽内部 `create_with_binding`create_project + import batch 共用「校验+防重+探测+insert」缓解决策记录:211 TODOrelocate 不并入update 非 insert
- **原因/取舍**:① description 纯规则抽首段会撞徽章墙/多语言引导/TOC——抽出来是噪音语义抽取归 LLM**image 不能粗暴跳过**(修正原 plan 错把 image 归噪音)——架构图/截图是 description 关键信息一张顶千字只跳徽章shields.io/badge.fury 等域 + build/version/license/coverage 关键词);③ 采样不丢 image = F-06 不被 F-260614-05 阻塞但不留遗憾,两者配套;④ 批量只对勾选项跑 LLM远少于发现全量平衡速度质量⑤ 「子代理」= 轻量 complete 复用现有 `scan_project_with_ai` 路径,非 aichat ReAct 重 agent批量精修不值得上多轮
- **边界**:导入项目 status 默认 `planning`(对齐 create_project导入后手改预览表格只读name/desc/stack/已绑定标记,不展示 image导入后详情页改不关联 idea批量无实时进度条最终 toast 汇总(导入 N/跳过 MLLM 全失败 description 留空让用户手填(不喂噪音)。
- **状态**:📐 2026-06-14 设计定稿待实施6 决策经 3 轮讨论收敛,修正原 plan 两处草率:纯规则 description + 跳 image。📋 实现时df-project 加 `discover_projects`/`is_monorepo` + `collect_sample` 扩 images + 徽章过滤commands 加 `scan_directory_for_projects`/`import_projects_batch` + 抽 `create_with_binding`;前端 Projects.vue 加导入 modal + i18nscan.rs 单测(采样剥噪音/image 收集/monorepo/discover
### AI 工具绑定目录bind_directory 专用工具 + 工具层白名单同步 + prompt 禁冒充 [2026-06-14]
- **决策**:① update_project 工具白名单补 path/stack同步 db 新字段);② 新增 bind_directory 专用工具(绑定目录不走通用 update③ 系统 prompt 加约束:工具失败须明说,禁用替代操作冒充原意图成功。
- **原因/取舍**:① review AI 对话发现 update_project(path) 被工具层白名单拒db 字段加了但工具层漏同步AI 转而改写 description 却回复「已记录」冒充绑定成功误导用户。② 专用工具语义清晰,防 AI 走通用 update 捷径冒充。③ prompt 约束防单链 ReAct「自我圆场」幻觉失败时用替代谎报成功
- **状态**:✅ 2026-06-14 落地(白名单同步 / bind_directory / prompt 中英约束;编译全绿)。📋 待清:工具层白名单与 crud 白名单双份去重(详见经验记录)。
### 📋 项目管理 review 剩余问题与处理论证(供后续会话)[2026-06-14]
- **背景**:项目管理 review 12 条9 条已修①delete 软删 / ②回收站工具 restore+purge+list_trash / ③collect_sample spawn_blocking / ④normalize_path 抽公共 / ⑥i18n / ⑦parseStack 抽 utils / ⑧ConfirmDialog / ⑫create_project 加 path。剩余 4 条 + 1 新发现论证如下,设计视角取**全局 + 对称 + 优雅**,非局部最优。
- **值得改(全局必要 + 对称缺失)**
- **⑤ update 白名单双份**tool_registry update_project 硬编码 5 字段 vs crud allowed_columns已致一次 bug。对称论证两者**语义不同**——DB 白名单=SQL 安全列(含 id/created_at/idea_id 系统字段AI 白名单=业务可改子集。不能复制,应**派生**AI ⊂ DB减系统字段真相源在 DB 一处。当前平行两份不对称,必漂移。
- **⑩ scan_project_with_ai 无 LLM 超时**。全局complete 卡住占 `llm_concurrency` 全局 permit → 阻塞主对话/标题生成/知识提炼,不止单次扫描。对称:`stream_llm` 有 idle 120s timeout见「流式可靠性三重保险」complete 非流式却无——两套 LLM 超时策略不对称,应对齐。
- **🆕 create_project 与 bind_directory 绑定逻辑重复**tool_registry create:170 内联绑定 + bind_directory:220 重复,已标 TODOcommands/project.rs create/relocate 同样)。对称+优雅:绑定是单一子操作,应集中 df-projectnormalize_path 已归此create/bind/relocate 共用一个 bind fn。当前 create 内联 bind 破坏「同名操作同实现」的对称。
- **低优先(局部优化,降级兜底,过度反伤优雅)**
- ⑨ collect_sample 文件大小限制read_to_string 全读再截,实际 README/清单 <10KB 概率低。最多一行 `metadata skip >1MB`,不必过度。
- ⑪ parse_scan_result JSON 提取:单 JSON 对象 OK多段场景降级兜底空 desc+规则 stack已足够。
- **状态**:📋 待后续会话处理。优先级 ⑤⑩ + create/bind 去重(中,全局/对称必要)> ⑨⑪(低/可选)。
## 知识库df-evolve / 共享记忆层)
> 核心定位与设计决策。详细字段/参数级决策见各条目。
### 定位 + 被动 Service 退化 [2026-06-13]
- **决策**知识库df-evolve定位为**整个 DevFlow 的共享记忆层**——每个模块idea/task/workflow/review/chat既是知识生产者也是消费者而非孤立展示功能页。df-evolve 褫夺「自动进化引擎」角色Sprint 2 对抗论证已砍,自用阶段 ROI 低/过度工程),**退化为被动 Service 层**,只暴露 `search/retrieve/record_reuse/feedback/save` 供各模块调用;被动 Service 复用 df-ai 检索做消费侧EventBus 做事件驱动提示(非自动抓取)。
- **原因**:手脑(各业务模块)分离无学习能力;知识库做「肌肉记忆」中枢系统才越用越懂你。砍的是自动挖矿,非知识库本身。
- **状态**:📐 设计未实施(用户拍板定位)
### 沉淀审核机制知识状态机AI 只产草稿 [2026-06-13]
- **决策**:知识加 `status` 字段,状态机 `candidate → pending_review → published → archived`**AI 提炼的知识一律进 candidate绝不直接入正式库**;草稿进「待审核收件箱」,人工逐条编辑(内容/分类/标签)→ 发布或丢弃。`verified` = 发布审核时一次性人工标intake 决断动作,非 ongoing 评分)。沿用 IdeaRecord 的 `pending_review` 状态机模式。
- **原因**沉淀必须有人工把关用户要求「人工能够编辑或调整必须有这些过程」——AI 提议、人裁决、系统如实记,非黑箱自动学习。
- **状态**:📐 设计未实施(用户明确要求加审核机制)
### AI 提炼产出:字段集 + 置信度(唯一指标)+ 查重 [2026-06-13]
- **决策**AI 提炼一条 candidate 时产出——**内容字段**`kind`分类AI 判定 7 类之一)、`title``content``tags``source_ref`(原始证据片段);**质量指标**`confidence`High/Medium/LowAI 自评,**唯一质量指标**);提炼时另做**查重**(比对 published 库,重复则不产/标合并,非存储字段)。**克制边界**:质量指标只留 confidence——不加 generality/specificity/novelty 等维度(过工程化 + 多耗 token适用范围并入 `tags` 不单列 scope 字段。
- **原因**`kind``source_ref` 是 AI 必填但易漏的两项confidence 服务降噪 + 审核分诊 + 透明,但**不绕过人审门**high 也不自动发布)。
- **状态**:📐 设计未实施
### 透明化provenance 溯源 + 收件箱 + 注入告知 [2026-06-13]
- **决策**:① 每条知识标来源(哪次 Chat/task/review 产出 + 原始片段),可跳回——复用现有 `source_project` + `source_ref` 字段;② 「待审核收件箱」作明确信息渠道;③ 复用时显式告知本次注入了哪几条、为什么命中。
- **原因**用户要求「透明化让人们有很好的信息获取渠道」——不黑箱。provenance 字段现有模型已有,零新增成本。
- **状态**:📐 设计未实施
### 克制原则:宁缺毋滥,小步迭代 [2026-06-13]
- **决策**:① **检索注入保守**——精确匹配(标签/关键词)优先,语义模糊匹配**后做**top-N 限 1-3 条,置信不够一条都不塞;② **关联不自动推断**——IdeaGraph 自动聚类/关联发现**先不做**,只支持人工标注;③ **沉淀不主动监听全量事件**——仅「一键沉淀」或事件提示后「确认」才产 candidate**从小到大**——先 AI Chat 单点双向跑通验证手感,再串 review/idea。
- **原因**:用户要求「尽可能克制,不要做大胆的连接,从小到大」——贯彻 Sprint 2「scope 砍 60%」精神到知识库,避免重蹈「自动进化」过度工程覆辙。
- **状态**:📐 设计未实施(用户明确要求克制)
### 指标客观化reuse_count 唯一信号,撤销 effectiveness 人工评分 [2026-06-13]
- **决策**:知识排名/淘汰**只用 `reuse_count` 一个客观信号**(检索注入自动 +1**撤销 `effectiveness` 的人工 👍/👎 评分**主观、有摩擦、信号不准淘汰改客观——reuse_count=0 且超 N 天未用 → 提示归档。
- **演进**初版三指标reuse_count + effectiveness + verified共同排序权重 → 同日修正:用户指出 👍/👎 是「人为、主观、非准确」的非必要干预,撤销。「用过 ≠ 有用」的质量顾虑改由两层客观兜底——① intake 审核门(一次性决断)② 发现噪音直接删。召回不准根因在标签/搜索质量,靠 intake 打准标签解决。
- **状态**:📐 设计未实施
### 📋 分层落地 Tier 1/2/3 + 来源/去向审查 [2026-06-13]
- **需求**:知识库联动分三层——**Tier 1必做**AI Chat ↔ 知识库双向 + 手动录入(沉淀 + 检索注入 + reuse_count + 审核收件箱 + 状态机,**无人工评分****Tier 2串创作流带前置**ai_node 检索 prompt_template、工作流 NodeFailed → pitfall**仅失败时**)、决策记录 → architecture_pattern**前置:先补 df-traceability 持久化****Tier 3**evolve_engine 自动挖事件。
- **审查(来源)**:① **/review 源移出**——devflow 无代码审查功能df-stages/coding.rs 审查节点是 TODO 空壳);② **想法评估源存疑**——对抗论点是「一次性结论」非可复用知识;③ **决策记录源标前置**——`DecisionJournal` 所有 SQLite 查询 TODO 未持久化;④ **工作流源限定失败时**——运行日志≠提炼知识。
- **审查(去向)****Chat 轴是唯一 Tier 1 就绪消费端**(检索→注入对话/提示词ai_node prompt 注入属 Tier 2工作流无主动消费仅被动产 pitfall决策溯源消费半残。→ 来源/去向双收敛到 Chat 轴。
- **状态**:📐 待实施(先做 Tier 1Chat + 手动录入)
### 对外暴露MCP Server 双向协议 [2026-06-13]
- **决策**:知识库对外 API 采用 **MCP Server** 形式暴露,**双向**(读+写。Tier 1 先做 DevFlow 内部闭环Tauri IPC**命令层设计完全对齐 MCP 语义**Tier 1+ 套 MCP server封装已有 command。MCP 暴露:① **Resource (读)**list/search/get**Tool (写)**create_candidate**Tool (计数)**record_reuse。
- **原因**:① Claude Code 原生吃 MCP——本机主力工具零集成成本② Cursor 也支持 MCP**双向价值**:外部工具(尤其 Claude Code 做代码审查/重构时是高质量知识来源——审查结论→review_rule、踩坑经历→pitfall接 MCP 自动回流知识库等于开「第二来源入口」。克制Tier 1 不实现 MCP 本身,但 6 个核心命令search/list/get/create/update_status/record_reuse全部按可暴露设计。
- **状态**:📐 设计未实施Tier 1+ 事项)
### 矛盾知识处理:纯标签+内容自述source_project 仅溯源 [2026-06-13]
- **决策**:矛盾知识**不建冲突关系表、不加 scope 字段、不做 access control**。消歧靠 tags`["Go","微服务"]` vs `["Go","单体"]`+ content 自述适用范围 + source_project 仅作来源溯源展示不参与检索过滤。AI 提炼 prompt 加约束:「适用范围有限制必须在 content 或 tags 中标注」。零数据结构变更。
- **原因/取舍**:矛盾是少数场景,为 minority 建关系系统是过度工程source_project 若做绑定/过滤会提高维护门槛 + 降低通用知识复用率;检索 top-N≤3 返回时内容本身场景描述足够消费者判断。
- **状态**:📐 设计未实施
### AI 提炼触发 + 知识注入:可配置 [2026-06-13]
- **决策**:① AI 提炼**默认自动触发**(后台 detached task4 个配置项:`auto_extract`default true/ `trigger_mode`on_complete|on_idle|manual_onlydefault on_complete/ `min_messages`default 4/ `idle_timeout_ms`default 30000。② Chat system prompt 知识注入**加开关**`auto_inject: bool`default true关闭时首行返回空字符串零开销
- **原因/取舍**手动按钮依赖用户记得点→遗忘→空库死循环自动触发保证持续流入候选。但用户控制欲不同——4 配置项覆盖从「全自动」到「全手动」。每次 complete() <500 input/<200 output token可关零成本。注入开关给用户「先积累再开启 / 调试不被干扰」的控制权。
- **状态**:📐 提炼设计未实施 / ✅ 注入已实施Tier 1
### 检索方案LIKE + top-N向量检索提前到 Tier 1Phase 5.5[2026-06-13]
- **决策**Tier 1 检索用 SQLite `title/content LIKE '%query%'` + `ORDER BY reuse_count DESC LIMIT 3`;收件箱排序用 `CASE WHEN confidence 'high'→3/'medium'→2/'low'→1`(非纯 TEXT 字典序)。**向量检索从 Tier 2 提前到 Tier 1 同步实施**Phase 5.5),加 **Settings 开关**`vector_enabled`,默认 false
- **原因/取舍**:知识库核心消费场景是 AI 自动注入(拿用户自然语言 query 检索非关键词精确搜索——「部署后白屏」匹配不到「Nginx SPA 路由」,纯 LIKE 语义盲区从第一天就存在。开关化解「本地优先/零依赖」哲学冲突:默认关闭纯 LIKE 零外部调用,开启后才走 embed API。
- **实施细节**:① `LlmProvider` trait 加 `embed()`OpenAICompat 实现Anthropic 不支持);② V8 幂等补 `embedding BLOB`f32 小端序列化NULL=未嵌入走 LIKE**嵌入时机=发布时**candidate 不浪费 embedpublished 才参与检索);④ 纯 Rust 余弦(<50k 条暴力遍历够用,不引 sqlite-vec 避免 Windows C 扩展编译风险);⑤ 三层降级链开关关→LIKE开但 provider 缺/embed 失败→自动回 LIKE正常→混合检索双信号>LIKE 单>向量单cos≥0.3 滤噪)。
- **状态**:✅ Tier 1 LIKE + 向量混合检索Phase 5.5)均已实施,编译通过待实测
### 知识生命线:独立 knowledge_events 表(非 JSON 嵌主表)[2026-06-13]
- **决策**:知识产生/审核/引用/归档四类审计事件存**独立 `knowledge_events` 表**V10 迁移),而非塞进 `knowledges.context_json` 字段。
- **原因/取舍**:事件是追加型(只增不改删),语义与主表 CRUD 完全不同一条知识可被引用数百次JSON 嵌主表致行膨胀 + 写更新竞争(每次引用都重写整行)。独立表可建 `(knowledge_id,event_type)` 复合索引事件表写失败只丢审计、不影响知识本身fire-and-forget 隔离)。未来加新事件类型只加一行 insert不动主表 schema。
- **状态**:✅ 已实施V10 迁移 + KnowledgeEventsRepo + 前端生命线时间线)
### 📋 知识详情页 + 编辑能力candidate 审核闭环)[2026-06-13]
- **需求**:卡片点不开详情、不能编辑、看不到「为什么产生」。详情页需呈现完整生命线 + candidate 可编辑修正后发布。
- **决策**Knowledge.vue 重构为 Ideas 式左右分栏(左卡片列表 @click 选中 / 右详情面板四分区:①基本信息 ②溯源 ③引用记录 ④生命周期时间线);可编辑字段 title/content/tags/confidence/reasoning 走 `knowledge_update`(部分更新)。
- **原因/取舍**candidate 编辑是审核闭环刚需——AI 提炼必有水分/措辞瑕疵,只能原样发布或整条拒绝会让审核空转。详情复用 Ideas 已验证的 master-detail 模式。published 编辑不做(有归档+重提炼替代)。
- **状态**:✅ 已实施(编译+vue-tsc+df-storage 21 单测全绿GUI 实测待 #54
## AI Chat 上下文窗口与并发控制(架构结论)
> 实现细节见 [归档文档](./功能决策记录-归档-2026-06-14.md)「AI Chat 上下文窗口与并发控制」。
### 设计决策 [2026-06-13]
- **ContextManager 类型替换为 messages 真相源**`AiSession.messages``Vec<ChatMessage>` 改为 `ContextManager`(非 wrapper 包装层),消息真相源唯一,避免 Vec + ContextManager 双存导致状态分裂。裁剪仅影响发送视图(`build_for_request` 返回裁剪版,`all_messages_clone` 返全量落库)。
- **淘汰算法:分组滑动窗口 + 三元组保护**`Assistant(tool_calls) + Tool(result)* + Assistant(final_text)` 工具调用三元组作为原子整体;最后 6 条≈2 个完整用户轮次)设为保护区永不淘汰。
- **Token 计数零依赖**`chars_count × 0.35` 粗估(误差 ±15% 可接受),不引入 tiktoken-rs5MB BPE 数据文件对 Tauri 打包不友好)。
- **双层 Semaphore 并发控制**AppState `LlmConcurrency`——全局并发默认 3 / 单对话默认 2permit 在 3 个叶子 LLM 调用点 acquire`run_agentic_loop` stream_llm 前、`generate_title_via_llm``extract_knowledge_from_conversation`),工具执行不受控。`per_conv` 实为应用级单一信号量(因 AiSession 单例 + generating 互斥,命名宽泛但当前语义正确,多对话路线时改 HashMap。Semaphore 重建用「软收敛」策略(替换内层 Arc旧 permit 不受影响)。
- **裁剪策略与模型选择正交**ContextConfig 不含 mode/模型选择字段;「高精度/低精度对话」属 LLM 调用层参数,与裁剪策略是正交维度。
- **状态**:✅ 已落地2026-06-13cargo check + vue-tsc 通过,待 tauri dev 实测)
## i18n
### legacy:false + zh-CN 默认 + locale 拆分 + glob 聚合 [Sprint 7 + 2026-06-13]
- **决策**:① `legacy:false` / `globalInjection` / zh-CN 默认 + en fallback / `localStorage df-language` 持久化。② `zh-CN.ts`/`en.ts` 单文件 → `zh-CN/*.ts` + `en/*.ts` 按模块拆分nav/dashboard/ai/common/settings/ideas/knowledge/projects/projectDetail/tasks/aiChat/aiTool`index.ts``import.meta.glob('./*.ts', { eager, import: 'default' })` 自动聚合(排除 index 自身)。新增模块文件即生效,不改 index。
- **原因**:① Composition API 模式;默认中文贴合自用,英文兜底。② 全量 i18n 接入8 view ~640 处中文)用多代理并行,模块隔离零冲突(每代理建自己模块 + 改自己 view不动共享 index比单文件扩 key多代理改同一 `zh-CN.ts` 冲突更适合并行。glob eager 运行时聚合,动态新增模块即拾取。
- **状态**:✅ Sprint 7 + 2026-06-13curl 验证 vite 正确展开 globvue-tsc PASS
### 状态枚举 i18nconstants 存 keyview 包 $t
- **决策**`constants/project.ts``PROJECT_STATUS_LABELS`/`TASK_STATUS_LABELS` 值从中文文案改存 i18n key`planning: 'projects.status.planning'``projectStatusLabel`/`taskStatusLabel` 返回 keyview 显示处包 `$t(projectStatusLabel(x))``PRIORITY_LABELS`P0/P1是代号非文案不动。
- **原因**constants 是纯数据/结构层,不应含展示文案(违反分层);文案归 localeconstants 管映射结构。
- **状态**:✅ 已落地
### 📋 项目 status 字段语义混乱(生命周期 vs 开发阶段)
- **现象**:后端 `projects.status DEFAULT 'active'` + `list_active`/软删除active/deleted 生命周期),但前端 `PROJECT_STATUS_LABELS` 是 planning/in_progress/paused/completed/cancelled开发阶段两套塞一个 status 字段。DB 实际只有 `status='active'`(新建默认,阶段值从没产生),前端 map 不认 → 显示英文 "active"。
- **治标**:补 `projects.status.active`(🚀进行中),不再显示英文。→ 根本未除。
- **治理方向**:① 后端支持阶段流转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 层)
- **决策**:窗口位置/大小/最大化用 `tauri-plugin-window-state` 插件Rust 层自动接管),而非前端 localStorage + setPosition/restore 方案。
- **原因**:插件自动覆盖主窗口 + 动态创建的 `ai-detached` 子窗口,零前端代码、零竞态;前端方案需手动同步且对子窗口生命周期处理复杂。需配套 `window-state:default` capability 权限。
- **状态**:✅ Sprint 10
### detached/docked 不持久化
- **决策**UI 布局持久化,但 `detached`/`docked` 重启后强制回 `false`,不随 `df-ai-ui` 落盘。
- **原因**:重启后分离窗口必然不存在,若恢复为 `true` 会让 UI 状态指向不存在的窗口按钮失灵、panelOpen 错乱)。这两个态是运行时临时态,不属可恢复布局。
- **状态**:✅ Sprint 10
> UI 布局 localStorage + 模块级恢复的细节见 [归档文档](./功能决策记录-归档-2026-06-14.md)。
### 消息列表虚拟滚动:自研 → 彻底移除 [2026-06-17 → 2026-06-18]
- **决策**AI Chat 消息列表虚拟滚动选**自研方案**IntersectionObserver + sentinel + ResizeObserver仅渲染层裁剪**→ 2026-06-18 彻底移除**(删 useAiVirtualScroll.ts + AiChat.vue 移除全链路),消息恒渲染。
- **原因/取舍**
- **不选 vue-virtual-scroller**——DynamicScroller 接管滚动容器 DOM + 重排子节点,破坏 `.ai-messages` flex/gap 布局 + onMessagesScroll(isNearBottom/scrollToBottom)/流式滚到底部既有逻辑。
- **自研只做渲染裁剪**原方案——sentinel 占位保 scrollHeightIO mount/unmount 可见区间外消息pinnedKeys 保活流式末条。
- **→ 彻底移除的取舍2026-06-18**:①**IO/RO 时序致重叠(移除主因)**——IO 判可见 + RO 测高度异步回调与 Vue 响应式交织,卸载分支 height=0 时 minHeight fallback 仍有竞态窗口reply1 移出 pinned + bubble 重建时 RO/IO 捕获 height=0 → slot 塌 0 → 后续上移重叠);多次修 fallback8abcd56+ 禁用裁剪0ca5d98验证重叠仍偶发shouldRender 恒 true 时 IO 仍设/清 sentinel inline minHeight 竞态源未除。②**消息量级不需要**——单会话几十条,恒渲染无性能问题。③**简化优于优化**——删 175 行 composable + AiChat 5 处调用,消除时序竞态源。
- **状态**:✅ 2026-06-17 落地e38474b→ ❎ 2026-06-18 彻底移除(工作区待提交:删 useAiVirtualScroll.ts + AiChat.vue 移除 import/解构/setupVirtualScroll/watch lastStreamingRenderKey/template :ref+shouldRenderMsg 条件)
## 应用启动 / 数据库配置
### Dev 与 Build 拆分独立数据库 [2026-06-13]
- **决策**`lib.rs` 启动时按 `cfg!(debug_assertions)` 选 DB 文件名——debugDev 模式)用 `devflow-dev.db`releaseBuild 模式)用 `devflow.db`,两库同处 `app_data_dir()``top.1216.devflow`)下,靠文件名区分。
- **原因/取舍**原启动代码无编译模式分支Dev 与 Build 共用一个 `devflow.db`——Dev 频繁改动/清空会污染 Build 侧真实运行数据。拆分后 Dev 库可随意折腾Build 库长期保留作运行效果基线。**文件名区分而非子目录**——改动最小lib.rs 一行 if两库平铺同目录便于备份/查看。**现有 `devflow.db` 文件名未变归 Build**零迁移零数据丢失Dev 首次启动自动建空库。
- **边界**`app_data_dir()``tauri.conf.json``identifier` 决定、与编译模式无关故拆分前两种模式确读同一文件。docs/使用手册备份命令 `cp devflow.db` 仍正确(备份 Build 真实数据)。
- **状态**:✅ 2026-06-13 落地(`lib.rs:24`
## UI 反馈与弹层
### toast/confirm 自建,不引 Arco / 不用 window.confirm [Sprint 10]
- **决策**Settings 页轻量提示与删除确认用自建 `toast`(顶部 fixed3s 自动消失)+ `confirmDialog`(遮罩 + 卡片Promise 化),而非引入 Arco Message/Modal 或原生 `window.confirm`
- **原因**:① `@arco-design/web-vue` 虽在依赖但 `main.ts``app.use` 注册,引 Message/Modal 要补全局注册 + 样式加载,过重违反做减法;② `window.confirm` 在 Tauri webview2 带「来自 localhost:端口」来源信息,无法去除,体验差。自建零依赖、样式可控(主题色)、`await confirmDialog()` 语义贴近原生 confirm。
- **演进** [2026-06-13]AiChat 删对话需确认 → 第二处复用落地。抽成 `src/components/ConfirmDialog.vue``visible`/`msg`/`dangerLabel` props + `@result` emit。按钮样式内联自包含不依赖外部 `.btn-*`——因 Settings 是 `scoped`,组件拿不到其内定义的 `.btn-danger`。选 SFC 组件而非 `useConfirm()` composable模板/遮罩/Transition 动画/CSS 才是真正重复主体Promise 封装留在调用方(~8 行)。
- **状态**:✅ Sprint 10
## 技能 / 联想
### 首批 Claude 3 类 + path 去重
- **决策**:技能联想首批数据源 = Claude skills / commands / plugins 三类SKILL.md frontmatter按 path 去重(`cache/``marketplaces/` 重复)。
- **原因**frontmatter 格式统一(`name`/`description`/`user_invocable`可统一解析Codex frontmatter 一致后续可扩展openclaw 属 agent 选择层不纳入。
- **状态**:✅ Sprint 8待实测
## 决策治理产品化评估2026-06-13
> 审视 DevFlow 是否应把决策治理(记录/锚点/完成度/自检/漂移)做成产品功能。机制设计详见 [规格契约自检机制-2026-06-14.md](../专项设计/规格契约自检机制-2026-06-14.md)。
### 5 痛点产品内未覆盖,真实运转的寄生 Claude Code 层 [2026-06-13]
- **决策**DevFlow 产品内对决策治理 5 痛点「设计满格、代码两极」——df-traceability 死代码(无表/无 IPC/无前端);契约锚点/AI 自检/漂移检测=规格契约自检机制-2026-06-14.md 纯设计稿0 行代码);完成度无聚合视图。唯一真实运转的(功能决策记录-2026-06-14.md + decision-record skill + dr-check hook寄生在 Claude Code 协作层,未沉淀进产品。
- **原因/取舍**:没用 Claude Code 的用户DevFlow 给不了任何决策治理能力。这套能力寄生在协作工具上,核心价值未进产品。
- **状态**:📐 待产品定位决策
### df-traceability 是锚点雏形Sprint 2 被砍(死代码可复活)[2026-06-13]
- **决策**`crates/df-traceability/``Annotation.location`(文件路径+行号)= 规格契约自检机制设计的「代码锚点」雏形,`Decision` struct 精确对应决策记录痛点。但 Sprint 2 对抗论证时被砍/降级,此后无表、无 IPCquery 方法全 `vec![]`
- **原因/取舍**:非显然关联——文档层设计的活契约+锚点机制,本质是产品外部用更轻方式重发明被砍的 df-traceability 轮子。是否复活取决于产品定位抉择Sprint 2 砍的理由(优先级低/过度设计)现需重新评估。
- **状态**:📐 待评估
### 产品化推荐路径 C 混合,完成度驾驶舱起步 [2026-06-13]
- **决策**三路径——A 全产品化(复活 df-traceability 全栈+spec 自检 AI成本大/重蹈 Sprint 2 覆辙风险B 纯寄生(承认是 Claude Code 协作层,只优化 skill/hook产品核心价值存疑**C 混合推荐——产品做数据底座decisions 表+完成度聚合+视图AI 验证/漂移留协作层**。最小起步:只做完成度驾驶舱(痛点 3
- **原因/取舍**C 分离「确定的数据层」与「不确定的智能层」,先做确定的低风险项。完成度驾驶舱起步:①最痛(记不住做了/没做②技术已存在tasks/ideas 有 status缺聚合 IPC+Dashboard 视图)③立刻可见④验证真会用再扩(避免 Sprint 2 式膨胀)。
- **状态**:📐 待用户拍板DevFlow 要否成为「决策治理/完成度驾驶舱」产品)
## crate 治理 / 模块结构2026-06-14
> 跨 crate 的删留与拆分决策。涉及 df-evolve 领域保留决策的推翻、coordinator 空壳的去留、ai.rs god file 的拆分方式。
### 删除 5 个零引用 crate推翻 df-evolve 领域保留决策)[2026-06-14]
- **决策**:整删 5 个 crate——`df-evolve` / `df-plugin` / `df-stages` / `df-task` / `df-traceability`。**推翻既有「df-evolve 领域类型保留」决策**:连同 `Knowledge`/`PromptTemplate`/`ReviewRule` 领域类型一起整删,知识库领域统一走 `df_storage::models::KnowledgeRecord`,不再维护独立领域模型层。
- **原因/取舍**
- **零引用铁证**:全仓跨 crate 引用为 0——`src-tauri/src` 下 0 处 `use`,其他 crate `Cargo.toml` 不依赖,仅 `src-tauri/Cargo.toml` 声明 `df-evolve` 但源码零用。整坨孤立骨架。
- **推翻归档决策**`功能决策记录-归档-2026-06-14.md:355` 原记「`Knowledge`/`PromptTemplate`/`ReviewRule` 领域类型有意保留待 Tier 1 Service 复用」——本次决定**放弃 Tier 1 独立领域模型路线**。理由:① 知识库 Tier 1 已在 `KnowledgeRecord`df-storage上落地 LIKE + 向量混合检索 + 状态机,实际运转的领域模型就是 `KnowledgeRecord`df-evolve 的领域类型成为「理想但悬空」的另一套定义,重复且误导;② 维护两套领域类型是「未来可能复用」的预期成本 vs 「现在重复定义 + 误导性地雷」的实际危害,用户选消灭后者。
- **df-task::Task 一并删**`Task`(含 `branch_id`/`tags`/`estimate_hours` 等比 `TaskRecord` 更丰富的字段)作为「理想任务领域模型」长期悬空零引用,任务领域统一用 `df_storage::models::TaskRecord`
- **df-traceability 整删**:原被记为「锚点雏形可复活」(见上方「决策治理产品化评估」)。本次决定删除——如未来真需锚点机制,重新评估而非保留死码。死码「可复活」是一种伪期权,实际价值是误导后续维护者以为它在运转。
- **df-plugin / df-stages 属过早设计**df-pluginWASM/动态库、df-stages11 阶段节点 `execute()` 全空壳未启动即删与「人机协同设计基准」中「AI 反复读写的代码不养空壳」一致。
- **影响**:① `src-tauri/Cargo.toml` 需移除 `df-evolve` 依赖声明;② 「决策治理产品化评估」中 df-traceability 相关条目(锚点雏形 / 完成度驾驶舱数据底座)状态需重新标注为「无现存代码可复用」;③ 知识库功能域(`## 知识库`)所有决策继续适用,但实现载体明确为 `KnowledgeRecord` 而非 df-evolve 领域类型。
- **状态**:✅ 2026-06-14 落地(用户拍板删 5 crate + 领域类型)
### coordinator.rs 保留B 路线占位空壳,不删)[2026-06-14]
- **决策**`crates/df-ai/src/coordinator.rs``AgentCoordinator`(多 agent 协作占位空壳,`run()` 返回硬编码 TODO**保留不删**加注释标注「B 路线待立项」。
- **原因/取舍**:与 aichat 升级 A/B 路线拆分一致——A 路线先做 UX 快赢已进行B 路线单独立项补多 agent 协作决策能力。coordinator 是 B 路线的入口锚点,**有意保留占位**而非删除:① B 路线立项时有现成挂载点trait + 结构骨架),不必从零设计;② 与上述 5 crate 删除不矛盾——5 crate 是「无路线图占位的纯死码」coordinator 是「有明确后续路线B 路线)的占位」,二者判断标准不同。区别在「是否绑定明确的演进路线」。
- **状态**:📐 B 路线待立项(保留空壳 + 加注释)
### ai.rs 拆分:低风险子 module 而非下沉 crate [2026-06-14]
- **决策**`src-tauri/src/commands/ai.rs`2663 行 god file拆成 `commands/ai/` 子 module 目录11 个文件mod + commands + agentic + stream_recv + conversation + title + audit + skills + prompt + tool_registry + knowledge_inject`pub use` 保持 `state.rs`/`lib.rs`/`knowledge.rs` 引用路径零改动。**采用低风险子 module 方案而非下沉到 df-ai crate**。
- **原因/取舍**:① 2663 行单文件消耗大量 AI context每次理解靠结构与「人机协同设计基准」中「组件拆分从『不做』升为『值得做』」一致。② **选子 module 不选下沉 crate**——下沉到 df-ai 需动 crate 依赖图df-ai 反向依赖 src-tauri 的 state/types引入循环依赖风险 + 跨 crate 重构成本;子 module 仅在同 crate 内切目录,`pub use` 保持对外 API 不变,零引用路径改动,重构面最小。③ 子 module 仍能达成「职责单一」的 AI 可读性目标,与下沉 crate 收益相当但风险低一个数量级。
- **实施验证2026-06-14 落地)**
- 11 文件合计 2833 行,最大 knowledge_inject.rs 557 行含测试commands.rs 529 行17 个 IPC 命令),其余均 < 400 行。
- 路径契约全保:`commands::ai::{AiSession, build_ai_tool_registry, restore_pending_approvals, spawn_embedding_for_knowledge, trigger_extraction_now}` + 17 个 invoke 命令state.rs/lib.rs/knowledge.rs/commands/mod.rs **零改动**
- `cargo check` 0 error`cargo test ai::` 19 passed。剩 3 warning 全为预存非本次引入。
- 顺带修 bug`ai_conversation_list``models` 字段从 JSON 字符串直塞改为解析为 `Vec<String>` 数组下发(前端期望数组,原代码传字符串)。
- **状态**:✅ 2026-06-14 落地11 子 module + glob 重导出 + models bug 修复cargo check/test 通过)
### 前端 ai.ts 拆分路线Astore 留 state 单例 + composable不引入 Pinia [2026-06-14]
- **决策**`src/stores/ai.ts`758 行 god store拆成 store 骨架(留 reactive state 单例 + 模块级私有变量)+ `src/composables/ai/` 下 6 个 composable`useAiEvents`/`useAiStream`/`useAiSend`/`useAiConversations`/`useAiWindow`/`useAiPanel`)。`useAiStore()` 统一入口展开所有 composable 方法,**返回 shape 不变,组件零改动**。
- **原因/取舍**
1. **与项目既有 store 风格一致**——project/knowledge/settings 全是「手写 reactive + 工厂函数」模式,引入 Pinia 会破坏一致性、增加心智负担。
2. **组件零改动**——AiChat.vue/AiDetached.vue 等用 `const { state, sendMessage } = useAiStore()` 解构,保持 `useAiStore` 返回 shape 不变即可。
3. **选路线 Acomposable 挪逻辑、store 留 state而非路线 BPinia 多 store**——后者要改 19 个 state 字段归属和所有组件 import风险远大于收益。
4. **与后端 ai.rs 子 module 拆分配对**——前后端 god file/god store 同步拆解采用各自生态的惯用拆法Rust 子 module / Vue composable不强求统一模式。
- **影响**:确立项目 store 架构约定(手写 reactive + composable不用 Pinia影响所有未来 store 设计。
- **状态**:🚧 执行中workflow w20yb6n6b 编排vue-tsc 自验证)
### AI trait 下沉:拆 df-ai-core 轻层,确立全局 AI 接入标准 [2026-06-14 📐]
- **决策**:将 `LlmProvider` trait + AI 数据类型(`ChatMessage` / `CompletionRequest` / `ToolDefinition` 等)从 `df-ai` 拆出,下沉到新轻量 crate `df-ai-core`(零 http 依赖);`df-ai` 保留为「实现 + `ModelRouter` + provider 工厂」hub持有 `reqwest`/`openai_compat`/`anthropic_compat`);所有消费 crate`df-ideas` 接对抗评估、未来 `df-knowledge`/`df-workflow` 等)**只依赖 `df-ai-core` 的 trait不直接依赖 `df-ai`**;真实 provider 由 `src-tauri` 最上层装配注入。**F-260614-03 及后续所有 AI 接入点统一照此,不再 per-module 自定义 trait。**
- **原因/取舍**(纯 ai-coding 工作模式下重算 F-03 原 A/B/C 选型):
1. **砍「人审查的契约面」而非「打字量」**——一份全局 trait = 一份契约给人过目 + 规格契约 self-check 锚一点per-module 自定义 trait原 A= N 份发散契约,审查负担与漂移风险同涨。纯 ai-coding 下接线/mock 全由 AI 吸收,故 A 的「适配器成本」、B 的「mock 成本」论点作废。
2. **保住纯逻辑 crate 零摩擦自检**——`df-ideas`/`df-storage` 只依赖 trait 不拖 `reqwest`,规格契约机制 A/B 测试自动跑无 http 依赖;裸 Bdf-ideas 直接依赖 df-ai会污染纯逻辑 crate、自检摩擦上升。
3. **与现有 roadmap 对齐**——`ModelRouter`F-260614-01、多 Provider 负载均衡池F-260614-04、provider 工厂(已落地)本就在 df-ai 内走「gateway」方向消费方选型应顺此而非另起 N 个 trait。
4. **无环且与 ai.rs 拆分不冲突**`df-ai-core`trait+类型)← `df-ai`(impl) / `df-ideas`(use trait)`src-tauri` 装配。「ai.rs 子 module 不下沉 crate」规避的是 src-tauri→df-ai 反向依赖;本决策是 df-ai→df-ai-core 正向拆分,方向相反、互不矛盾。
- **否决项**:纯 Aper-module trait= 全局 N 份发散契约,反模式;裸 Bdf-ideas 直接依赖 df-ai= 纯逻辑 crate 被 reqwest 污染、自检摩擦上升。
- **退路**:若不愿加新 cratetrait 可放 `df-core`语义稍糙——LLM 非领域类型,但零新 crate可接受
- **状态**:📐 设计定稿2026-06-144 项决策已定:① 拆分边界=仅 trait+数据结构 ② provider 注入=构造注入 `Engine::new(Option<Arc<dyn LlmProvider>>)` ③ LLM 失败=自动降级启发式+warn+`EvaluatedBy` 标记 ④ provider 构造=应用层 src-tauri 装配注入),未实施。详见 [F-07-df-ai-core-trait下沉设计-2026-06-14.md](../已编号方案/F-07-df-ai-core-trait下沉设计-2026-06-14.md)。
### df-core → df-types 改名:类型库非核心,语义明示「类型契约层」[2026-06-17]
- **决策**crate `df-core` 改名为 `df-types`。全 workspace 机械改名54 处源码引用 + git mv + 9 个 Cargo.toml 依赖声明 + Cargo.lock 自动迁移)。
- **原因/取舍**
- **「core」名称误导**——df-core 含 0 业务逻辑、0 内部依赖、0 宏自引用,纯粹是跨 crate 共享的类型定义ProjectRecord / TaskRecord / IdeaRecord / KnowledgeRecord / ChatMessage 等)+ re-export 聚合。叫「core」暗示它是核心业务层实际是「类型契约层」。
- **改名收益 > 成本**——54 处机械替换纯字符串无语义改动git mv 保历史Cargo.lock 跟随 Cargo.toml 自动更新。一次性 10 分钟操作,消除后续所有新贡献者的认知摩擦。
- **不影响 df-ai-core**——df-ai-core 是 AI trait 层LlmProvider / CompletionRequestdf-types 是领域模型层ProjectRecord 等存储实体),两者职责清晰不重叠。
- **状态**:✅ 已落地commit 4be1591
## 工作流人工审批节点B-03
### HumanNode 审批响应机制subscribe→send→select! 广播过滤等待 [2026-06-14 📐]
- **决策**df-workflow `HumanNode.execute` 改为「先 `subscribe()` → 发 `HumanApprovalRequest``tokio::select!` 循环等 `HumanApprovalResponse`」,按 `execution_id + node_id` 双键过滤命中后返回 NodeOutput`select!` 三分支 = 响应 / 超时(配置 `timeout_secs` 默认 3600s/ 取消500ms 轮询 `is_cancelled`)。复用既有 `EventBus`(broadcast) / `HumanApprovalResponse` 事件 / `approve_human_approval` IPC / 前端 store——零新增基础设施仅改 HumanNode 一处。
- **原因/取舍**
1. **订阅时序铁律**tokio broadcast 不回放历史,必须 `subscribe()` 先于 `send(Request)`,否则 receiver 错过 Response 死等超时。
2. **双键过滤**node_id 单键不够跨工作流可能重复、execution_id 单键不够(同层多 HumanNode双键才完备。
3. **Lagged 容忍**capacity 256 + 审批低频,漏自身 Response 概率极低;`continue` 优于丢弃(丢弃误判超时更糟)。
4. **decision 强制校验**options 非空时强制 `decision ∈ options`非法值报错而非静默放行——审批门控不能被脏输入绕过options 空时允许自由文本。
5. **B-06/B-07 是并发隔离/取消的前置,非单流功能前置**:单工作流 B-03 照常工作;并发安全等 B-06execution_id 下沉);取消机制需 B-07 + `set_cancelled` + cancel IPC单列 **B-03b**。B-03a响应等待 + 超时)不依赖 B-07。
- **边界**:取消分支在 B-07 + `set_cancelled` 补齐前恒 false等价无取消功能不残跨工作流并发 HumanNode 在 B-06 修前有 Response 错配风险。
- **状态**:📐 设计完成2026-06-14未实施。详见 [B-03-人工审批响应机制-2026-06-14.md](../已编号方案/B-03-人工审批响应机制-2026-06-14.md)。
## 任务推进链7 态状态机 + 工作流联动)
> tasks 表从 todo→done 的状态推进链路。阶段17 态状态机 + advance_task CAS 原子写 + 软删除已落地阶段2工作流联动task_id + 完成回调 advance_task + DAG 模板)进行中。详细实施路径见 [任务推进链实施路径-2026-06-16.md](../专项设计/任务推进链实施路径-2026-06-16.md) / [推进链阶段2实施路径-2026-06-16.md](../专项设计/推进链阶段2实施路径-2026-06-16.md)。关联决策 D-260616-01~04前端7态对齐 / 软删除 / Node trait 归属 / 阶段1先行
### advance_task / 状态机走 df-nodes Node trait不复活 df-taskD-260616-03
- **决策**:任务推进业务逻辑(`can_transition_to` 状态机 / `advance_task` 原子写 / 闸门节点)落在 **df-nodes crate 的 Node trait 扩展**`task_state_machine.rs` / `task_advance_node.rs`IPC 层 thin 入口。不复活 2026-06-12 刚因零引用删除cf017f8的 df-task crate不塞 commands/task.rs。
- **原因/取舍**:① 对齐 D3「业务逻辑在 df-nodes 实现Node trait 纯接口df-workflow/src/node.rs:67」原则② 复活一个零引用刚删的 crate 是制造新死码df-nodes 补 `df-storage` 依赖读 TaskRecord 即可(核实无循环依赖);③ IPC 层保持 thintask.rs 仅 3 行转发)守住 D3 不让业务逻辑下沉 IPC。前端对齐后端 7 态D-260616-01types.rs:131激活 InReview/Testing/Blocked 三闸门态)。
- **状态**:✅ 阶段1 落地commit d2cb38c7态状态机 + advance_task CAS + 软删除25 测试);🚧 阶段2 进行中batch32注册 TaskAdvanceNode + config 下沉 + HumanNode reject
### DagExecutor config 下沉:节点级覆盖全局级 deep_merge④-1
- **决策**`DagExecutor.run` 构造 `NodeContext.config` 从「`initial_config.clone()` 覆盖一切」改为「`deep_merge(node_def_config, initial_config)`」——节点级配置覆盖全局级节点定义优先。Dag 加 `node_configs: HashMap<NodeId, Value>`build_dag 填入 NodeDef.configrun 合并下沉。
- **原因/取舍**原实现executor.rs:99-107用全局 initial_config 覆盖 NodeDef.config**节点级配置被完全忽略**——TaskAdvanceNode.execute 读 `ctx.config.task_id` 拿到全局 config 而非节点定义写的 task_id节点参数化失效。这是阶段2 的架构前置阻塞点:不修则 TaskAdvanceNode 无法从 DAG 接收 task_id。选「节点级覆盖全局级」节点定义优先而非全局覆盖节点级因节点是更具体的配置源。deep_merge 对 Object 递归合并,非 Object 节点级直接覆盖。现有 HumanNode/AiNode 也受益(它们当前读 ctx.config 拿全局 config但无人通过 NodeDef.config 定义节点参数故未暴露)。
- **状态**:🚧 实施中batch32 ④-1 agentdag.rs + executor.rs + registry.rs + workflow.rs配 deep_merge / node_config_overrides 测试)。
### DAG 模板硬编码,不建 workflow_defs 表(②-6
- **决策**:任务推进的工作流 DAG 模板todo→in_progress / in_review→testing / testing→done 三条推进边 + 退回)**硬编码**在 `df-nodes/task_workflow_templates.rs`(导出 `template_for(target_status) -> DagDef`**不建 workflow_defs 表**。`tasks.workflow_def_id` 字段留 None。
- **原因/取舍**模板数量少且稳定3 条推进边 + 退回),建表需 CRUD UI + 版本管理 + 关联维护,过度工程。[业务系统设计-2026-06-12.md](./业务系统设计-2026-06-12.md) 确认 workflow_defs 从未建表(工作流定义 dag_json 内嵌 workflow_executions延续此约定。硬编码模板随代码版本管理零运行时配置开销。
- **状态**:📐 设计定稿待实施(②-6batch33+)。
### 工作流回调语义:成功与任务推进解耦,失败按 target 退回(②-3/②-4/②-5
- **决策**:工作流完成后回调 advance_task 的语义——**成功**executor Oktask_id + target_status 都 Some 时调 `advance_task_atomic`,回调失败只 warn 不回滚工作流(工作流已完成是事实,任务推进失败前端提示手动处理);**失败**executor Err按 target_status 推算退回态testing→in_review / in_review→in_progress调 advance或加 `failure_target_status` 参数。HumanNode reject②-5从 Ok 改返 Err使审查拒绝走 failed 触发退回。
- **原因/取舍**工作流成功与任务推进是两个独立事实解耦避免「工作流成功但任务推进失败时回滚已完成工作流」的复杂性失败退回让审查拒绝能回流上一态review_rounds+1。CAS 已防回调与手动 advance 并发撞advance_task_atomic 捕获 InvalidState 降级。跨表事务缺失阶段2 回调失败降级阶段3/4 补 Database.transaction())。
- **状态**:📐 设计定稿待实施batch32 做 ②-5 HumanNode reject 语义化batch33 做 ②-3/②-4 回调)。
### AiNode 自审闸门:内部 return Err 复用 executor first_err方案 A[2026-06-17]
- **决策**AiNode 自审闸门选**方案 AAiNode 内部 return Err**——自检失败时 return Err(SelfReviewFailed) 复用 DagExecutor 已有的 first_err 收敛 + ②-4 回调错误路径。不选方案 BDAG edges 条件 + ConditionEngine依赖暂缓的 T-260614-11和方案 CDagExecutor 核心循环改闸门钩子。gate 配置默认 false 向后兼容阶段2 行为不变testing 模板可设 gate:true 启用。
- **原因/取舍**
- **方案 A 零 executor 核心改动**——Err 沿既有 execute() → run_node() → first_err 路径自然冒泡executor 核心循环零行变更。②-4 回调已处理 failed 分支(退回上一态),闸门失败自动走此路径无需额外代码。
- **方案 B 依赖 ConditionEngine**——T-260614-11 条件表达式引擎尚在 📐 设计阶段,为单个闸门功能拉入未完成的依赖链路风险高。
- **方案 C 改 executor 核心循环**——在 node 执行前后插钩子before/after execute是通用扩展点但当前仅 AiNode 一个消费者,为单一场景改核心循环过度工程;且钩子语义(是否中断后续节点、是否影响 DAG 继续执行)需详细设计,复杂度远超方案 A 的 1 行 return Err。
- **状态**:✅ 已落地commit e16d038
## 需求与待办
> 汇集散落于各决策条目状态(📐/🚧)的待办 + 新增需求细节 + 需求澄清。单一清单,避免遗漏。
### 📋 待做需求
| 需求 | 功能域 | 来源 | 优先级 |
|---|---|---|---|
| Sprint 9/10 多项编译过未 tauri dev 实测(评分 IPC 缩放 / update_full / promote_idea / Store getter | 灵感/立项/Store | Sprint 910 🚧 | P1 |
| 切对话不中断路由:部分场景运行时实测 | AI Chat 可靠性 | Sprint 8 🚧 | P1 |
| 技能联想「使用」:首批 3 类联想已做,联想后实际触发/执行技能未实现 | 技能/联想 | Sprint 8 | P2 |
| 灵感对抗评估接 LLM论点/evidence 由 df-ai LlmProvider 生成(现启发式 fallback | 灵感模块 | Sprint 9 📐 | P2 |
| 知识库 Tier 1AI Chat ↔ 知识库双向闭环(沉淀+检索注入+reuse_count+审核收件箱+状态机+provenance 溯源+克制检索,无人工评分) | 知识库 | 2026-06-13 ✅ 已实施(Sprint 15) | P1 |
| 路径校验根治workspace 白名单 + canonicalize现仅拒 `..` + 敏感目录) | 工具调用 | Sprint 6 | P2 |
| 停止生成 idle 即时优化:`tokio::sync::Notify` 替代 120s 轮询 | AI Chat 可靠性 | Sprint 6 | P3 |
| 多 Provider 负载均衡池(备用模型/多账号聚合,全局容量=min(各 provider 上限之和, global_cap) | AI Chat 并发控制 | 2026-06-13 📐 | P2 |
| 裁剪/压缩消息按需召回Query Function + 分层存储: TrimRecord 追踪被移除范围 → DB 全量归档按需检索 → 精准注入 build_for_request触发方式待定:自动/手动/语义检索) | 上下文窗口管理 | 2026-06-13 📐 | P3 |
| ✅ IPC参数驼峰/蛇形不对齐误报澄清Tauri v2 自动将前端 camelCase 参数名转后端 snake_case`approve({toolCallId})` / `setConcurrencyConfig({globalLimit})` 实际正确、功能正常——无需修 | AI Chat | 2026-06-13 审查误报 | — |
| 🔴 df-workflow ConditionEngine 默认 true所有未识别条件表达式均通过工作流条件分支形同虚设`Ok(false)``Err` 一行可修 | 工作流引擎 | 2026-06-13 代码审查 | P0 |
| 🔴 df-workflow DagExecutor execution_id 硬编码 "dummy-execution-id":所有执行 ID 相同,追踪/审计失效 | 工作流引擎 | 2026-06-13 代码审查 | P1 |
| 📐 df-workflow HumanNode 假实现execute 注释"等待审批"但首次迭代直接 return "同意" — **设计完成 [B-03-人工审批响应机制-2026-06-14.md](../已编号方案/B-03-人工审批响应机制-2026-06-14.md),待实施**(依赖 B-06 并发隔离 / B-07 取消前置) | 工作流引擎 | 2026-06-13 多代理探索 → 2026-06-14 设计 | P0 |
| 🔴 df-workflow NodeRegistry::default() 的 script 工厂 unimplemented! panic用 default() 构建注册表 + 跑 script 节点即崩溃进程(非优雅 Err | 工作流引擎 | 2026-06-13 多代理探索 | P0 |
| 🔴 df-workflow executor 每节点拿全新空 StateMachineself.state_machine 从不传入 NodeContextHumanNode is_cancelled 恒 false取消机制失效 | 工作流引擎 | 2026-06-13 多代理探索 | P1 |
| 🟡 promote_idea 两步写非事务INSERT project 成功后若 UPDATE idea 失败,项目存在但想法状态未变,补偿删除可修 | 灵感/立项 | 2026-06-13 代码审查 | P1 |
| 🔴 分离窗口detached跨窗口状态失效用 localStorage 传递生成态快照df-ai-gen/textTauri 多 webview 不共享 localStorage 致静默失效;用户点 X 关闭(非 closeDetachedWindow后主窗口 `detached` 永真卡死reattachPanel 死代码未接线)。需改 Tauri 全局 emit/listen 同步 + 窗口销毁事件复位 | AI Chat 分离窗口 | 2026-06-13 代码审查 | P1 |
| 模型能力声明与自动路由系统 Phase 1ModelCapability 数据模型 + ModelRouter 重写 + 7 调用点接入 + Settings 模型池编辑 UI + AiChat 模型下拉。核心:按任务需求(模态/功能/成本)自动匹配合适模型,不再所有场景共用 default_model | 模型能力与路由 | 2026-06-13 📐 设计完成 | P1 |
| 模型能力系统 Phase 2多模态消息支持——ChatMessage.content: String → Vec<ContentPart>(Text/Image);前端粘贴/拖拽图片vision 模型自动路由 | 模型能力与路由 | 2026-06-13 📐 | P2 |
| 模型能力系统 Phase 3Agent 内智能路由——Agentic Loop 每轮按子任务构造不同 TaskRequirements成本预算控制模型级联降级跨 Provider 搜索 | 模型能力与路由 | 2026-06-13 📐 | P3 |
| 📋 已澄清「显示多开」= 多会话来回切可对话(非 AI Chat 窗口多开)[2026-06-17]:用户原意是「多个会话之间切换都可继续对话」(当前切换会话后旧会话生成态丢失)。关联 F-09 多会话架构决策——A 路线(单例 AiSession + 软隔离Sprint 8 已落地切对话不中断路由待实测验证是否满足需求T-260614-02B 路线真多会话AiSession 单例→多实例)是备选但触及 memory 记录的「AiSession 单例未动」架构约束。→ 2026-06-17 澄清为「多会话并发」需求,推荐先实测 A 路线再定是否需 B 路线。已记 todo L652 + 待决策.md🟡 A/B 路线决策) | AI Chat / 多会话 | 2026-06-13 待澄清 → 2026-06-17 已澄清 | 🟡 待 A/B 路线决策 |
| 🔴 待审批持久化根治(重启恢复)未生效——两处逻辑断裂致恢复链路跑不通:① `ai_conversation_switch` 无条件 `pending_approvals.clear()` 清空 `restore_pending_approvals`(init)重建的内存 HashMap`ai_pending_tool_calls`/`ai_approve` 均依赖内存态 → 重启后前端 `switchConversation` 触发 clear → 审批卡片查空永不显示、审批报"未找到挂起的审批";② `ai_approve` 的 recovered 守卫跳过 `save_conversation`(注释称"防空 messages 污染老对话"前提不成立——switch 时 `restore_from_messages` 已载完整历史,审批时 messages 非空 → 执行的工具结果不落库,重启后 toolCard 显示 completed 但 result 仍是占位"需要用户审批,等待确认"。修复方向pending 恢复链路改查 DB`ai_tool_executions` WHERE status='pending' 持久化真相源)绕过内存 clear`ai_approve` 内存 miss 时 fallback DB 单条重建再执行recovered 审批通过后正常 save。可顺带删 `restore_pending_approvals`DB 即真相源)。阻断用户"功能逻辑层面解决"诉求——现"根治"实为表面修复 | AI Chat 审批持久化 | 2026-06-13 /review 审查①② | P0 |
| 📋 审批可见性缺口pendingApprovals 无兜底渲染→卡死 [2026-06-13]AI 发起 Med/High 工具审批AiApprovalRequired后暂停等审批不发 delta前端审批唯一出口是 ToolCard 的 pending_approval 内联卡片(靠 findToolCall 置 tc.status但 state.pendingApprovals 数组有数据却零渲染AiChat.vue 仅 @approve 转发,无 pendingApprovals 模板)。若 tc 卡片未显示审批,用户看不到审批按钮 → AI 永久等 → 文字停卡死。待修A. AiChat.vue 加 pendingApprovals 醒目渲染(顶部条/浮层)兜底审批可见性;或 B. 运行时确认 tc 卡片是否渲染。配套watchdog 在 AiApprovalRequired 暂停,审批没弹则 watchdog 盲点,需加"审批超时未响应"提示 | AI Chat 审批 | 2026-06-13 | 📋 A/B 待定 |
| 📋 node_executions 全表 list当前只写不读若未来前端要看某次工作流执行的节点明细需**新增** `list_node_executions(execution_id)` 命令 | 工作流引擎 | 2026-06-13 代码审查 | 📐 待需求驱动 |
### 📋 需求澄清
- **「决策」术语边界**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 排查教训
- [功能决策记录-归档](./功能决策记录-归档-2026-06-14.md) — 纯流水/老 Sprint/UX 微调/已被取代
- `PROGRESS.md` — 各 Sprint 工作流水与遗留
- [Phase 2 计划](../07-项目管理/Phase2计划-2026-06-12.md)

View File

@@ -0,0 +1,407 @@
# 功能决策记录 — 归档
> 从 [功能决策记录-2026-06-14.md](./功能决策记录-2026-06-14.md) 归档的条目——纯实现流水、老 Sprint 决策、UX 微调、已被取代或合并的细节。这些条目在「3 个月回看是否仍影响系统/功能设计理解」判断下已不再需要常驻主文档,但完整保留以备回溯。
>
> 创建2026-06-14 | 性质:归档只读,不再维护更新
## 归档判断标准
- 一次性代码审查流水(甄别落地、骨架删除、列表查询过滤)
- UX 微调(工具卡片折叠、对话滚动、错误友好化、空白屏修复)
- 实现细节参数解析抽函数、token 记录策略、model 追溯)
- 老 Sprint 决策已被新设计取代或合并
> 其中**架构级结论**已提炼一句留在主文档对应小节,归档条目为细节展开。
---
## 一、AI Chat 上下文窗口与并发控制 [任务 #43 系列]
> 主文档保留一句架构结论:**ContextManager 类型替换为 messages 真相源 + 双层 Semaphore 并发控制(全局 3 / 单对话 2**。以下为实现细节归档。
### ContextManager 接线方式:类型替换而非 Wrapper [任务 #43]
- **决策**`AiSession.messages` 字段类型从 `Vec<ChatMessage>` 直接改为 `ContextManager`,后者成为消息的唯一持有者(内含 `Vec<TrackedMessage>` + token 缓存 + 裁剪能力)。不引入 Wrapper 包装层。
- **原因/取舍**Wrapper 方案下 Vec 是真相、ContextManager 是临时视图——每次 `build_for_request` 都要 clone 给 ContextManagertoken 缓存永远滞后一轮或每次重建(双重复制)。类型替换让 push 即刻更新 token 计数、零额外 clone、单一数据源。代价是 `save_conversation` 等需全量的场景要显式调 `all_messages_clone()`多一行但语义明确。13 处操作点中 6 处签名不变push/clear、4 处微调clone→all_messages_clone/iter、2 处需重写。
- **状态**:✅ 已落地2026-06-13cargo check + vue-tsc 通过,待 tauri dev 实测)
### Token 计数方案:字符粗估零依赖 [任务 #43]
- **决策**:用 `chars_count × 0.35` 粗估单条消息 token 数(~2.8 字符/token每条消息 +4 token 固定开销role 标记),每个 tool_call +30 tokenJSON 结构)。不引入 tiktoken-rs 或任何 tokenizer 依赖。
- **原因/取舍**:用途是「发送前判断是否超限」做预算控制,误差 ±15% 完全可接受tiktoken-rs 引入 BPE 数据文件约 5MB对 Tauri 桌面应用打包不友好provider 返回的 usage 可用于事后校准闭环暂不实施(原 calibrate 方法未接线Review 时已删,留待未来按 provider usage 重做。chars_ratio=0.35 对中英混合文本偏保守(纯英文 ~0.25,纯中文 ~0.5-0.7),宁可多算不少算。
- **状态**:✅ context.rs 已落地2026-06-13校准闭环暂缓
### 淘汰算法:分组滑动窗口 + 三元组保护 [任务 #43]
- **决策**超预算时从最旧消息开始按「淘汰单元」丢弃。Standalone 消息单独成单元;`Assistant(tool_calls) + Tool(result)* + Assistant(final_text)` 工具调用三元组作为原子整体要么全保留要么全丢弃。最后 6 条消息≈2 个完整用户轮次)设为保护区永不淘汰。
- **原因/取舍**:工具调用三元组若拆散会导致 LLM 看到工具调用但找不到对应结果(或反之),产生幻觉重复调用。保护区防止丢失即时上下文。替代方案是直接按消息数截断(简单但破坏三元组),或按 token 截断到某位置(可能从三元组中间切断)。分组滑动窗口在安全性和信息保留间取平衡。
- **状态**:✅ 已落地2026-06-13
### 裁剪时机build_for_request 时裁剪push 不触发 [任务 #43]
- **决策**token 预算检查和消息裁剪仅在 `build_for_request()` 构建请求时执行。`push()` 只做追加+计缓存,不做任何淘汰。
- **原因/取舍**Agentic Loop 一轮执行中会多次 pushassistant 回复 → tool_result → 可能再 assistant这些属于当前活跃轮次的消息绝不能被中途裁剪掉。只在「即将发给 LLM」这个时间点评估并裁剪旧消息语义清晰且安全。
- **状态**:✅ 已落地2026-06-13
### 持久化策略裁剪仅影响发送视图DB 存全量 [任务 #43]
- **决策**`all_messages_clone()` 返回全量未裁剪消息用于 save_conversation 落库;`build_for_request()` 返回裁剪后版本发给 LLM。两者解耦。
- **原因/取舍**:用户切换对话回来期望看到完整历史,不应因自动裁剪而永久丢失。裁剪是临时的「发送时压缩」类似 gzip。未来若要做永久摘要压缩将旧对话提炼为 summary 消息插入),那是独立 feature 不影响此设计。
- **状态**:✅ 已落地2026-06-13
### 多工具调用并行化join_all 无上限 [任务 #43]
- **决策**`process_tool_calls` 中 Low 风险工具收集后用 `futures::future::join_all` 并行执行Med/High 审批工具仍串行(需用户交互)。工具执行本身不限并发数(本地操作)。
- **原因/取舍**LLM 一次返回 N 个独立工具调用(如同时读 3 个文件)时,串行执行 = O(N×T),并行 = O(T)。N=3 时节省 ~600ms/轮。工具执行是本地 I/Oread_file/grep 等),无外部限流风险故不加 Semaphore。仅 LLM 调用受并发控制。
- **状态**:✅ 已落地2026-06-13
### LLM 并发控制:双层 Semaphore [任务 #43]
- **决策**AppState 新增 `LlmConcurrency`(封装两个双层 `Arc<Mutex<Arc<Semaphore>>>`——全局并发默认 3 / 单对话默认 2。permit 在 3 个叶子 LLM 调用点 acquire`run_agentic_loop` 内 stream_llm 前、`generate_title_via_llm``extract_knowledge_from_conversation`),作用域结束自动释放;工具执行不受控。`LlmConcurrency: Clone`(两 Arccheap作为参数串到底spawn 函数收 by-value move、叶子收 `&ref`。acquire 顺序固定 global→per_conv所有调用点一致防死锁。
- **原因/取舍**:多对话场景下同时跑 2-3 个对话可能撞 provider RPM 限制导致 429。双层控制全局防总并发失控单对话防单对话独占标题生成 + 主循环 + 提炼并发)。用 `Arc<Mutex<Arc<Semaphore>>>` 双层包装而非裸 `Arc<Semaphore>`——tokio Semaphore permits 构造时固定不可增减,替换内层 Arc 即重建,已持有旧 permit 不受影响。permit 放叶子调用点(最接近真实 HTTP 调用)而非 command 入口,限流粒度精准且不阻塞非 LLM 路径。
- **状态**:✅ 已落地2026-06-13
### per_conv 实为应用级单一信号量(非 per-conv map[任务 #43 / Review 修正]
- **决策**`LlmConcurrency.per_conv` 字段命名暗示「单对话」并发,但实现是应用级**单一** `Semaphore`(非 `HashMap<conv_id, Semaphore>`)。当前不修实现,仅在 `state.rs` 结构体 doc 标注真实语义 + 未来重构路径。
- **原因/取舍**`AiSession``Arc<Mutex<AiSession>>` 单例,且 `generating` 互斥保证同一时刻仅一个对话的 `run_agentic_loop` 在跑。故 per_conv 实际退化为「单对话内并发」(主循环 stream_llm + 标题生成 + 知识提炼三者受限流约束)——命名虽宽泛但当前语义恰好正确,非 bug。改 HashMap 是过度设计(单例会话下多 map 项永不被并发访问)。风险留待未来:若支持多对话并发 loopper_conv 需随之改 `HashMap<conv_id, Semaphore>` 才名副其实。揭示 `LlmConcurrency``AiSession` 单例的隐式耦合——本次 review 最有价值的发现。
- **状态**:✅ 注释留痕2026-06-13 Review 修正state.rs 结构体 doc 标注;实现未改,非 bug多对话路线时重构
### 裁剪策略与模型选择正交 [任务 #43 / 架构边界]
- **决策**:上下文窗口管理的 `ContextConfig` 不含 `mode`/模型选择字段。「高精度/低精度对话」(深度思考/reasoning_effort/模型选择)属于 LLM 调用层参数(`CompletionRequest` 层面与裁剪策略sliding_window / 未来 summarization是**正交维度**,不混入 ContextManager。未来摘要压缩作为独立模块实现。
- **原因/取舍**避免把不同层面的控制拧到一个配置对象里——ContextConfig 只管「窗口多大、怎么裁」,模型/推理模式由调用方在构建 `CompletionRequest` 时决定。职责单一,后续扩展任一维度不影响另一侧。
- **状态**:✅ 架构边界已划定2026-06-13 讨论确认)
### save_conversation 异步化 [任务 #43]
- **决策**:循环结束时 spawn 异步落库,先释放 `generating=false` + emit `AiCompleted`,不阻塞前端收到完成事件。
- **原因/取舍**:当前 save_conversation 同步执行upsert + JSON 序列化),阻塞 Completed 事件几十~几百毫秒。落库失败不影响已完成的结果展示,异步化提升用户感知响应速度。代价是进程崩溃时最后一轮可能未落库(概率极低且下次启动可从 LLM provider 侧无法恢复 anyway
- **状态**:✅ 已落地2026-06-13
### build_for_request 只传 token 数值,不传 system_prompt 文本 [任务 #43 / Review 修正]
- **决策**`build_for_request(sys_tokens: u32) -> (Vec<ChatMessage>, bool)` 只接收 system prompt 的预估 token 数,不接收 `&str` 文本。system_prompt 的 token 估算在调用方(`ai.rs`)完成。
- **原因/取舍**职责单一——ContextManager 负责裁剪和消息管理,不需要知道 system prompt 的文本内容。避免每次调用传递可能很长的字符串(含知识库+技能注入后可达几千字符),且 `build_for_request` 内部不混入估算逻辑。
- **状态**:✅ 已落地2026-06-13
### join_all 不对 tool_calls 做 sort保留原始 index [任务 #43 / Review 修正]
- **决策**:收集 Low 风险工具时保留原始 `(index, draft)` 元组,不额外 `sort_unstable_by_key`。LLM 返回的 tool_calls 已按 index 有序sort 是多余且可能打乱语义顺序。
- **原因/取舍**`futures::join_all` 保证结果顺序与输入一致,只要输入有序输出就有序。去掉 sort 减少一次 O(n log n) 且避免意外重排。回填 session.messages 时按原始 tool_call_id 匹配即可,不依赖数组位置。
- **状态**:✅ 已落地2026-06-13
### build_for_request 视图裁剪,不 mutate self.messages [任务 #43 / Review 修正]
- **决策**`build_for_request(&self)` 改不可变借用,超预算时构造裁剪**视图**返回(`self.messages[trim_end..]` clone`drain` 修改自身。删除原会 `self.messages.drain(0..trim_end)``trim_to_budget` 方法。
- **原因/取舍**:原 mutate 实现违背「裁剪仅影响发送视图」契约——`drain``all_messages_clone()`save_conversation 数据源)也返回裁剪版,长对话每轮丢历史、累积性数据丢失。视图裁剪每轮 build 重算 trim_endO(n) 遍历淘汰单元n 通常 <50 可忽略),换全量持久化不被破坏。
- **状态**:✅ context.rs 已落地2026-06-13 Review 修正5 单测通过)
### AiSession.messages 类型替换为 ContextManager + 13 处接线 [任务 #43 / Part A 第二块]
- **决策**`AiSession.messages``Vec<ChatMessage>` 直接替换为 `ContextManager`(类型替换,非 wrapper 包装层。13 处操作点适配push/clear/len/iter 签名天然兼容零改动switch 对话改 `restore_from_messages(Vec)``replace_tool_result` 自由函数删除改走 `ContextManager::replace_tool_result_content` 方法DRY 收敛,方法已含 token 重估save_conversation 与 ensure_conversation_title 改 `all_messages_clone()` 取全量。
- **原因/取舍**:类型替换而非 wrapper 层——消息真相源唯一,避免 Vec + ContextManager 双存导致状态分裂ContextManager 方法签名刻意与原 Vec 操作对齐push/clear/len/iter13 处中 6 处零改动,改动面最小。核心改造点 run_agentic_loop 由 `session.messages.clone()` 全量塞入改为 `build_for_request(sys_tokens)`:调用方用 `TokenEstimator::estimate_text(&system_prompt)` 估算 system prompt tokenContextManager 只接收数值不接触 prompt 文本。
- **状态**:✅ ai.rs 已落地2026-06-13cargo check 通过 + df-ai context 5 单测绿)
### 裁剪触发时不 emit 前端事件 [任务 #43 / Part A]
- **决策**`build_for_request` 返回的 `trimmed: bool` 暂忽略(`let (history_msgs, _trimmed) = ...`),裁剪发生时不向前端 emit 任何事件。
- **原因/取舍**:裁剪是无损优化——内存 `all_messages_clone` 与 DB 持久化均保留全量历史,仅发送给 LLM 的视图裁掉旧消息。计划原拟用 `AiError` 通知前端,但 AiError 语义是「错误」,裁剪不是错误会误导用户以为出错;前端无需感知裁剪(对 UX 透明)。未来若需「已压缩早期历史」提示条,新增专用事件(如 AiContextTrimmed而非复用 AiError。
- **状态**:✅ ai.rs 已落地2026-06-13
### B1 Low 风险工具 join_all 并行 + 串行回填 [任务 #43 / Part B]
- **决策**`process_tool_calls` 重写——批量发 Started 后Low 风险工具 `futures::future::join_all` 并行 execute闭包内完成即 emit Completed/Error不持 session 锁),`join_all` 返回后串行 push tool_result + audit持锁。Med/High 审批逻辑不变。
- **原因/取舍**:原 for 循环逐个串行 executeN 个独立 Low 工具 = N 倍等待;并行化总耗时 ≈ 最慢一个。execute + emit 放闭包内完成即通知前端体感逐个出结果push/audit 必须持 session 锁故留 join_all 后串行。`join_all` 保序——结果顺序 = 输入顺序 = tc_list sort 后的原始 index 顺序tool_result 回填不乱序。
- **状态**:✅ ai.rs 已落地2026-06-13
### B1 附注tool_result push 顺序变化无语义影响 [任务 #43 / Part B]
- **决策**Med/High 占位 tool_result 先于 Low 结果 push分类阶段先处理审批占位Low 结果 join_all 后回填),与原「按 tc_list 交错顺序 push」不同。
- **原因/取舍**LLM 按 `tool_call_id` 关联 tool_result不看消息绝对位置连续 tool_result 都是 ContextManager 的 ToolResultTail、归同一三元组顺序不影响语义。两阶段占位先行 + 结果后填)比交错处理实现简单。
- **状态**:✅ ai.rs 已落地2026-06-13
### B2+B3 正常完成save/title/extract 打包后台 spawn [任务 #43 / Part B]
- **决策**`run_agentic_loop` 正常完成段,把 save_conversation + maybe_spawn_extraction + ensure_conversation_title 三者打包进**同一** `tauri::async_runtime::spawn` 后台 task主流程只做 `generating=false` + emit Completed。stop 路径2 处save 保持同步 awaittitle 改 `spawn_ensure_title` 后台。
- **原因/取舍**:① 计划原拟裸 spawn save`maybe_spawn_extraction` 明示「需在 save 之后(读已落库消息)」——裸 spawn save 与 extract 各自独立 task 顺序不保证extract 可能读到旧 DB打包同一 task 内 `save → extract → title` 串行 await 保顺序。② stop 路径 save 保持同步stop 后用户可能立刻发新消息触发新 loop两 loop 的 save 并发 upsert 会竞态(读旧值叠加丢 token正常完成段同风险但频次低、最多丢少量 token 累加(非功能错误),可接受。③ `ensure_conversation_title` 签名从 `provider: &dyn LlmProvider` 改收 `provider_config: &AiProviderRecord`、内部自建 provider——`&dyn` 非 'static 无法 move 进 spawn收 config 克隆进 task 后自建。
- **状态**:✅ ai.rs 已落地2026-06-13并发场景待实测
### Semaphore 重建采用「软收敛」策略 [任务 #43]
- **决策**:用户在 Settings 调整并发上限时,通过 `*semaphore = Arc::new(Semaphore::new(n))` 替换整个 Arc 内部值。已持有旧 permit 的任务不受影响,新请求走新限制。缩并发时实际并发 = 旧持有数 + 新上限(软收敛非硬切断)。
- **原因/取舍**tokio Semaphore 的 permits 数只能在构造时设定,运行时无法增减,这是标准限制。替代方案(如用 Mutex+计数器手动实现复杂度高且易出错。「软收敛」行为可接受——用户调低并发后进行中的请求不会被中断只是新请求受控待旧请求释放后新限制完全生效。UI 可提示「已有 N 个进行中请求」改善体验。
- **状态**:✅ 已落地2026-06-13
### protect_count=6 保护最近约 2 个用户轮次 [任务 #43]
- **决策**:淘汰算法保护最后 6 条消息不纳入淘汰单元。依据:每轮典型产生 User + Assistant[±tools] + Tool* ≈ 2~5 条6 条 ≈ 覆盖 2 个完整轮次。
- **原因/取舍**固定数值简单可靠。不改为动态轮次检测增加复杂度且轮次边界模糊——Assistant 纯文本 vs 带 tools 的 Assistant 消息算同一轮还是不同轮。6 是保守值,宁可多保几条也不要误裁活跃上下文。未来可根据实测调整。
- **状态**:✅ 已落地2026-06-13
### syncConcurrencyConfig 加 debounce 防快速连续 IPC [任务 #43]
- **决策**Settings 页面修改并发数值后,通过 debounce~300ms延迟调用 `ai_set_concurrency_config` IPC防止快速连续拖动滑块/按键时频繁重建 Semaphore。
- **原因/取舍**`@change` 在 input[type=number] 上只在失焦时触发已比 `@input`但用户可能快速点「保存」或连续调整两个值。Semaphore 重建虽轻量Arc::new但不该无节制地做。手写简易 debounce~5 行)即可,不需引入 lodash-es 依赖。
- **状态**:✅ 已落地2026-06-13
### spawn 异步 save_conversation 加 warn 日志 [任务 #43]
- **决策**`tauri::async_runtime::spawn(save_conversation_inner)` 内部用 `if let Err(e) = ... .await` 捕获错误并 `tracing::warn!` 记录,不静默吞掉。
- **原因/取舍**spawn 的 task 错误默认被 tokio 静默丢弃,调试时完全看不到落库失败。加一行 warn 零成本出问题时能从日志定位。不影响用户体验warn 不是 error
- **状态**:✅ 已落地2026-06-13
---
## 二、AI Chat 工具卡片折叠 [Sprint 10 + 2026-06-13]
> 纯 UX 微调,全部归档。
### 双层折叠:卡片级 + 内容级分离
- **决策**`expandedCards`(整卡 body 显隐)与 `expandedTools`read_file 代码预览级)两套独立状态。
- **原因**:卡片折叠和文件内容预览是两个维度,合并会互相干扰。
- **状态**:✅ Sprint 10
### running/pending_approval 强制展开
- **决策**:执行中、待审批卡片不可折叠,始终展开。
- **原因**:用户需看到骨架屏(执行中)和审批按钮(待操作)。
- **状态**:✅ Sprint 10
### 新内容追加自动收起旧卡
- **决策**deep watch `messages` + 轻量 JSON snapshot diff 检测新内容(新消息 / toolCall 状态变化 / 文本增长)→ 清除旧 completed/rejected 展开态,保留活跃卡。
- **原因**:多步调用时旧结果折叠为单行 header界面紧凑类 ChatGPT/Cursor
- **状态**:✅ Sprint 10
### 首次加载 / 切对话不触发收起
- **决策**`isFirst` guard首次 snapshot 赋值后直接 return。
- **原因**:防切换对话或初次加载时把当前可见卡片误折叠。
- **状态**:✅ Sprint 10
### 短结果保持展开(审查①)
- **决策**`rejected`(拒绝原因)/ `write_file`(写入路径)短结果用 `shouldKeepOpen(tc)` 强制展开,仅 `read_file`/`list_directory`/通用 JSON 大体量结果折叠。
- **原因**:短结果折叠无紧凑收益,反隐藏关键信息(拒绝原因/写入路径)。
- **状态**:✅ Sprint 10
### 卡片宽度对齐气泡(审查②)
- **决策**:工具卡片 `max-width: 90%`,与 AI 文本气泡(`.ai-msg-bubble` max-width 90%)一致,不撑满 content 区。
- **原因**:卡片原默认 stretch 撑满 100%、气泡 90%视觉宽度不一致list projects 等卡片比回复气泡宽一截)。选限制卡片对齐气泡(非撑满气泡),保留气泡式留白;数据卡片 90% content 宽通常够展示文件树/代码(横向 `overflow-x:auto` 兜底)。
- **状态**:✅ 2026-06-13
### 折叠态 header 显示结果摘要 [2026-06-13]
- **决策**completed 工具卡片在 header 的 `.ai-tool-sub` 槽位(原仅 running 显示「执行中...」)追加结果摘要 `toolResultSummary(tc)`按工具返回结构提取一句话list_tasks→「12 项任务」、list_projects→「5 个项目」、list_ideas→「8 条想法」、create_project→「已创建xxx」、create_idea/task→「已创建title」、update_project→「已更新 status」、delete_project→「已删除」、run_workflow→「请到工作流页面运行」。`.ai-tool-sub` 加 ellipsis 截断防长标题撑破。
- **原因**list_tasks/list_projects/list_ideas/run_workflow 等工具无路径参数(不像 read_file/list_directory header 带路径),折叠态 header 仅剩工具名,光秃秃、信息密度低。摘要放 header非 body使折叠态也能一眼看到核心信息不必先展开。list_* 返回裸数组取 `.length`create 取 `.name`project/`.title`idea/task——字段不对称是后端 model 既定,摘要层各自适配。
- **状态**:✅ 2026-06-13
### 工具调用前 AI 总结文字去气泡 [2026-13](已回滚)
- **决策**AI 消息同时含文字 content 和 toolCalls 时,文字气泡加 `.ai-msg-bubble--plain`(去背景/边框/padding/圆角/max-width变紧凑纯文本紧贴卡片纯文字消息无工具卡片保留完整气泡。→ **已回滚**:恢复所有 AI 消息统一气泡。
- **原因**:原想条件去气泡兼顾紧凑与长回复可读性。→ 回滚原因:用户实测后强调「所有 AI 消息都应在气泡里」,条件去气泡造成两种 AI 消息外观(纯文字有气泡、工具总结无气泡)不一致、突兀。**紧凑不该靠去气泡实现**——牺牲一致性换紧凑得不偿失。未来若要紧凑走「气泡与卡片视觉一体」(卡片纳入气泡 / 连一体块 / 仅压间距)。
- **状态**:✅ 2026-06-13 落地 → 🔄 2026-06-13 回滚(一致性优先,布局方案待用户再定)
### 工具卡片区拆子组件ToolCard + ToolCardList [2026-06-13]
- **决策**:工具卡片区从 AiChat.vue原 1983 行)拆为两个子组件——`ToolCard.vue`单卡纯展示props: `tc`/`isExpanded`/`isContentExpanded`emits: `toggle`/`expand-content`/`approve`+ `ToolCardList.vue`(列表容器,持有折叠态 `expandedCards`/`expandedTools``defineExpose``collapseInactive`。AiChat.vue 仅 `import` + `ref` 持有列表 + 转发 `@approve``store.approveToolCall`
- **原因/取舍**:工具卡片占 AiChat.vue ~650 行(模板 107 + script 150 + CSS 392是高修改频率区折叠/摘要/时间轴等 UI 调整频发。拆出后卡片相关变更隔离在两文件1983 行主文件不再因 UI 调整反复动。auto-collapse 桥接选 `expose`+`ref`(方案 B而非 prop+emit / provide+inject——前者是 Vue3 标准模式、子组件自治折叠态、父级只调一个 `collapseInactive(activeIds)`。附带ToolCard 内用 `parsed` computed 缓存 `parseResult`(原模板 6 次重复 `JSON.parse`,每次 patch 都重解析);气泡与卡片的 `max-width:90%` 提取为全局 `--df-msg-max-width` 变量(原跨文件靠注释维系对齐,改一处忘一处即错位,变量化单一来源)。**深化(可维护性)**ToolCard.vue 再拆双 `<script>` 块——14 纯函数 + `ToolResult` 类型提模块顶层(只建一次,`setup` 聚焦 props/parsed`parseResult` `any``ToolResult|null`(防模板字段名打错编译期不报),`toolDisplayName` 去 i18n fallbackpath 几乎总有,属过度防御)。取舍:为可维护性/易迭代,不为微性能(省闭包可忽略)。
- **状态**:✅ 2026-06-13 落地(重构 Step 1-5 完成AiChat.vue 净减 ~650 行)
---
## 三、AI Chat Token 用量与模型记录 [2026-06-13]
> 主文档保留一句设计结论:**Token 分账本——对话 / 工作流节点 / 标题生成三处独立记账**。其余实现细节归档。
### 流式 token 落库走累加模式(非覆盖)
> 已移入 [经验记录-2026-06-14.md](./经验记录-2026-06-14.md)「约定」分组。
### Anthropic 流式 output_tokens 当累计值直接覆盖
> 已移入 [经验记录-2026-06-14.md](./经验记录-2026-06-14.md)「约定」分组。
### Token 展示默认关闭 + Settings 开关 [2026-06-13]
- **决策**AI 气泡底部 token 条默认不显示,加 `df-show-token-usage` 开关放 Settings 通用设置区,默认 `false`
- **原因**token 是计量信息非核心;用户对「头顶信号」敏感度不一,日常默认隐藏减干扰,需要时开。开关关闭时 store 不写 `lastTokenUsage`、DOM 不渲染,零视觉干扰。
- **状态**:✅ 2026-06-13
### 前端 token 双状态(单次 + 对话累计)[2026-06-13]
- **决策**store 维护 `lastTokenUsage`(最近一条回复)+ `convTokenTotal`(当前对话累计)两个状态,而非单一状态。
- **原因**:单状态切对话会错乱——实时值(当前回复产生的 tokenvs 历史总值(切回老对话应从 DB 读累计)混一起。双状态各司其职:切换时 `convTokenTotal` 从 summary 加载、`lastTokenUsage` 清空。
- **状态**:✅ 2026-06-13
### model 记录策略:取配置值 + 补填不覆盖 [2026-06-13]
- **决策**:记录的 model 取 `provider_config.default_model`(非 stream 响应解析);`save_conversation` insert 时写、update 时仅 `rec.model` 为 None 才补填。
- **原因**:① stream chunk 未必带 model 字段;`default_model` 是确定配置值且即请求所用(`request.model` 就用它发),配置名比响应里的具体版本号(如 `gpt-4o-2024-xx`)对用户更有意义。② model 字段 DB 早存在但老代码 insert 一直填 None补填兼容历史对话下次活动自动回填已有值不覆盖。
- **状态**:✅ 2026-06-13
### token 分账本:对话 / 工作流节点 / 标题生成 [2026-06-13]
> 此为设计结论,**已在主文档保留一句**:三处分属不同成本中心,混进 `ai_conversations` 会让对话 token 失真。
- **决策**:① 对话主流程 token`run_agentic_loop` 流式)落 `ai_conversations`;② 工作流 AI 节点 token 进 `NodeOutput.usage`(写 `node_executions.output_json`),不进对话表;③ 标题生成 token 刻意不记(`generate_title_via_llm``resp.usage` 丢弃)。
- **原因**:三处分属不同成本中心——工作流节点消耗属工作流执行成本,与对话 token 是两个账本,混进 `ai_conversations` 会让对话 token 失真标题生成是系统附带行为max 30 token量小计入对话总量会让用户困惑「只问一句怎么这么多 token」。非流式 `complete()` 的 token Provider 层已返回,仅消费侧按账本分流。
- **状态**:✅ 2026-06-13
### model UI 展示暂缓,留后续升级 [2026-06-13]
- **决策**model 已落库(`ai_conversations.model` + 前端 `AiConversationSummary.model` 字段就绪),但**暂不在对话旁展示**,留作后续升级项。
- **原因**:数据通路已打通(后端写值、前端字段在),展示是纯前端增量、随时可加;当前先把 token 用量展示做稳model 展示留到后续 UI 升级(如对话头部 / token 条旁)统一考虑,避免零散加。
- **状态**:📐 待实施UI 展示,数据已就绪)
### model 追溯:消息级 + 对话级多值C+B[2026-06-13]
- **决策**:消息级 `ChatMessage``model` 字段(每条 assistant 消息记生成它的 model+ 对话级加 `models` 字段JSON 数组,去重存对话用过的所有 model`model` 单值字段保留存首个(兼容已就绪的前端字段 + 补填不覆盖策略不动)。
- **演进**[2026-06-13 初版 📋] 现状为对话级单值 + 补填不覆盖,中途切换只留首个、消息级无字段无法追溯 → [2026-06-13 同日落地] 用户拍板 C+B。A对话级覆盖记最近未选——中途历史仍丢纯 C 不便快速看「用过哪些」,加 B对话级聚合互补。
- **原因**:「每一条对话都能有效记录」需消息级追溯(每条 assistant 消息知道谁生成);对话级 models 聚合便于不解析整条 messages 就知用过哪些(中途切换场景全覆盖)。`model` 单值保留是渐进兼容(前端 `AiConversationSummary.model` 已就绪,展示首个),不强删避免迁移破坏。
- **落地细节**:① `ChatMessage.model: Option<String>`serde default 兼容历史消息);② `run_agentic_loop` 构造 assistant 消息时 `msg.model = Some(provider_config.default_model)`;③ `save_conversation` update 路径 models 去重追加(读旧 JSON 数组 + 新值去重、insert 初始化 `[model]`;④ V6 迁移加 `models` 列;⑤ 前端 `AiMessage.model?` + `Summary.models?`switchConversation 解析历史消息 model。展示仍暂缓见上条
- **记录时机澄清 → [2026-06-13]**models 只追加**实际生成过内容**(本轮 `stream_llm` 跑过、产生 assistant 消息)的 save 调用,不记「配置但未实际用于生成」的 model。需求来自用户澄清「对话集应该不是切换过都留住吧只有实际发送过消息的才留」——首轮即 stop无 stream路径传 `Some(model)` 会把未生成的 model 误写进 models 数组。正确做法:`run_agentic_loop` 入口 `stop_flag` 路径(未 streamsave 传 `None`;仅 stream 后的退出路径(正常完成/stream 后 stop/审批暂停)传 `Some(model)`
- **状态**:✅ 2026-06-13编译/类型双过,未 tauri dev 实测) | ✅ 记录时机已落地2026-06-13入口 stop 路径 save 改传 `None`cargo check + vue-tsc 双过
---
## 四、AI Chat 对话与滚动 UX
> 滚动/错误友好化/空白屏修复/markdown 缓存为 UX 微调,归档。审批「删全屏 Modal 保行内」为设计决策,**已留在主文档**。
### 智能滚动:`isNearBottom` < 80px
- **决策**:仅当用户在底部 80px 内才自动滚动;上滑时不强制拉回。
- **原因**:用户回看历史时被强制拉回底部体验差。
- **状态**:✅ Sprint 8
- **演进** [2026-06-13]:上滑看历史时新消息/流式不滚 → 补「回到底部」浮动按钮。`showBackToBottom`(内容溢出 >100px 且不在底时显)+ `onMessagesScroll` 刷新 + `onContentChange`(在底则滚、上滑则只刷按钮不强制拉回)。沿用「不打断回看」原则,补一键回底入口。
### 错误友好化:`friendlyError` 正则映射
- **决策**404/401/timeout/network 正则映射为中文提示 + `isError` 红色气泡。
- **原因**:原始错误信息对用户不友好。
- **状态**:✅ Sprint 8
### 审批卡片显示参数内容 + 防双击 [2026-06-13]
- **决策**审批卡片pending_approval在按钮上方渲染工具参数键值对`toolArgsEntries` 遍历 args`formatArgValue` 截断超长)+ 风险提示reason`AiToolCallInfo``reason?` 字段AiApprovalRequired 回填。点击批准/拒绝后 store 乐观置 `status='running'` + 按钮 `:disabled="status!=='pending_approval'"`,等后端事件转 completed/rejectedIPC 失败回滚 pending_approval。
- **原因/取舍**:原审批卡片只有「批准/拒绝」按钮 + 工具名update_project/run_workflow用户**盲批**——参数不可见等于放行未知操作。双击无防护则连发 IPC后端 `pending_approvals.remove()` 有幂等不重复执行,但第二次报错弹窗)。乐观置 running 即时禁用按钮 + 回显参数,审批从「盲批」变「知情决策」。
- **状态**:✅ 2026-06-13
### 空白屏修复:防 FOUC
- **决策**`index.html` 加主题防闪烁脚本(渲染前置 `data-theme`+ `__APP_T0` 启动埋点 + body 背景色。
- **原因**Vite 启动慢122s期间 webview 白屏;前置主题脚本消除 FOUC。
- **状态**:✅ Sprint 7
### 流式 markdown 渲染加结果缓存 [2026-06-13]
- **决策**`renderMd``Map<text, html>` 缓存(限 200 项超出清空);`mdReady` 翻转marked+DOMPurify 加载完成)时清缓存重渲。
- **原因/取舍**`v-html="renderMd(...)"` 每次响应式触发(折叠/状态变化/新 delta都跑 marked.parse + DOMPurify.sanitize 全文。历史消息文本不变却重复全量解析O(n) × 触发次数)。缓存后历史消息命中 O(1);流式 currentText 中间态各缓存一项≤200 后清)。**未做**「流式中降级纯文本」——会让流式无格式、完成后突变格式,体验差;缓存方案保留流式格式且消除重复解析。
- **状态**:✅ 2026-06-13
---
## 五、AI Chat 对话管理 [Sprint 10]
### 侧边栏时间分组日历日桶today/yesterday/earlier
- **决策**:活跃对话按日历日分桶(今天/昨天/更早),归档对话单列可折叠区,而非滚动 24h 或纯时间倒序线。
- **原因**:用户心智「今天的对话」指当天而非过去 24 小时;归档是冷区,单列折叠减少对活跃区的视觉干扰。
- **状态**:✅ Sprint 10
### 归档分组默认折叠 + 持久化
- **决策**`archivedCollapsed` 默认 `true`(折叠),折叠态写入 `df-ai-ui` 持久化。
- **原因**:归档为冷区,默认折叠让活跃对话优先可见;持久化记住用户展开偏好,不每次重启都收回。
- **状态**:✅ Sprint 10
### 归档绕过 update_fieldset_archived 走原始 SQL
- **决策**:归档用专用 `set_archived` 方法直接 `UPDATE ai_conversations SET archived = ?1`,不复用 `update_field` 宏。
- **原因**`update_field` 对任何字段操作都强制连带 `SET updated_at = now`,归档是元数据切换不应改对话时间——否则归档后侧栏时间全跳成「刚刚」,破坏时间分组排序。归档与内容更新是两类操作,时间戳语义须区分。
- **状态**:✅ Sprint 10
### 恢复活跃对话加守卫 `!state.activeConversationId`
- **决策**`loadConversations` 自动恢复上次活跃对话时加守卫仅首次加载、ID 仍有效、当前无活跃对话才恢复)。
- **原因**:若用户已手动 new/switch恢复逻辑不应覆盖其当前意图守卫保证恢复只在「冷启动无状态」时介入。
- **状态**:✅ Sprint 10
---
## 六、AI Node工作流节点[任务 #44]
### AI Node 参数解析抽纯函数,单测不 mock LLM [任务 #44]
- **决策**`AiNode::execute` 内的参数解析config + 上游 inputs → `AiNodeParams`)剥离成独立纯函数 `parse_params(config, inputs)`execute 调用后再 `build_provider` + `complete``AiNodeParams``#[derive(Debug)]`unwrap_err 断言需要)。
- **原因/取舍**execute 原本 162 行硬编码 `build_provider`,直接单测会触真 HTTP——需 mock `LlmProvider` trait + 构造完整 `NodeContext`event_bus/node_status/NodeId重且脆弱。抽纯函数只接 `config` + `inputs`execute 实际用到的),参数解析/缺参报错/prompt 上游优先回退/默认值model 空→`gpt-4o-mini`、protocol→`openai_compat`)全可单测,零 mock 零网络。`complete` 调用正确性归 df-ai provider 层测试职责分层。副作用execute 瘦身parse 与 LLM 调用分离,可读性提升。
- **状态**:✅ 已落地2026-06-137 纯函数单测全过缺参×3 / prompt 取值×2 / 默认值 / 显式参数GLM 真调集成测试 `glm_live_complete``#[ignore]`+env var通过glm-4-flash 返回「通过」/usage 正确GUI DAG 触发实测转 → #54 跟踪)
### AI Node 参数边界值契约:空 model 走 provider 兜底 / prompt 双源 [任务 #44]
- **决策**config 无 model 时 `parse_params` 给空 `model` + `default_model="gpt-4o-mini"` 占位字段;`build_provider``default_model`(构造需非空),`CompletionRequest.model` 忠实传空串,由 provider `convert_request``if req.model.is_empty() { self.default_model }`兜底。schema `required``["base_url","api_key"]`,不含 `prompt`prompt 双源:上游 `inputs["prompt"]` > `config.prompt`)。
- **原因/取舍**:① `model`/`default_model` 双字段分离「忠实值」与「构造兜底」——provider 构造要非空 model空则后续无兜底锚点故 default_model 占位防 panic但 request 仍传真实 model让 provider 兜底单点收敛(两 provider convert_request 统一 `is_empty→default_model`),避免 AiNode 自猜默认与 provider 不一致。② prompt 双源支持「prompt 由上游节点产出」(如 read_file→ai 分析schema required 含 prompt 会误拦此合法配置。
- **状态**:✅ 已落地2026-06-13两 provider convert_request 兜底经 review 核实自洽schema required 改后 7 单测全过)
---
## 七、代码审查甄别2026-06-13 一次性审查流水)
> 全模块代码审查后的甄别落地。原则性结论(「真实 bug 修、简单清理做、规模不到位的优化先不动」)**已在主文档「需求澄清」保留**。以下是具体落地条目。
### df-evolve 骨架物理删除(连带三件套)
- **决策**:删 `KnowledgeStore` + `PatternExtractor` + `EvolveEngine` 三件全 TODO 空壳;同步删 df-nodes 5 个空节点docker/git/http/notify/subflow
- **原因/取舍**KnowledgeStore 被 EvolveEngine 唯一引用、PatternExtractor 同理——删前两者必连带删 EvolveEngine编译依赖原「删 2 个」甄别后扩为三件套。src-tauri Cargo.toml 声明依赖 df-evolve 但源码零 `use df_evolve`,整坨孤立骨架。`Knowledge`/`PromptTemplate`/`ReviewRule` 领域类型保留(有实现 + 测试Tier 1 Service 复用)。
- **状态**:✅ 2026-06-13cargo check 通过)
### 列表查询加过滤(内存策略)
- **决策**`ai_conversation_list` 加 limit默认 50+ include_archived默认 falseAI 工具 `list_projects`/`list_tasks`/`list_ideas` 闭包加 `truncate(50)``build_system_prompt` 项目注入 `take(20)``list_ideas` 命令加 status 可选过滤。均用内存 filter/take 或现成 `query`,不加新 SQL 方法。
- **原因/取舍**dev 阶段数据量小,内存过滤零触碰通用 `impl_repo!` 宏(改宏风险高)。数据真到成千上万再加 Repo 的 `list_limited` SQL 方法。`list_ideas` 复用现成 `query("status", v)` 走白名单,零改 Repo。前端 API 加可选参数,旧无参调用兼容(后端默认值兜底)。
- **状态**:✅ 2026-06-13
### node_executions「全表 list」甄别为伪命题
- **决策**审查提出的「node_executions 全表 list 改按 execution_id 过滤」**不成立**——全仓仅 state.rs 注册 NodeExecutionRepo无任何 list/query 命令对外暴露只写不读cargo `dead_code` warning 印证)。
- **原因**:不存在「废弃全表 list」的对象。若未来前端要看某次工作流执行的节点明细需**新增** `list_node_executions(execution_id)` 命令,属新功能非清理。
- **状态**:📐 待需求驱动新增
### ALLOWED_COLUMNS 从全局共享演进为按表隔离
> 已移入 [经验记录-2026-06-14.md](./经验记录-2026-06-14.md)「约定」分组。
---
## 八、状态持久化UX 偏好部分)
### UI 布局localStorage + 模块级恢复
- **决策**面板布局panelOpen/maximized/sidebarOpen/archivedCollapsed`df-ai-ui``restoreUiState()` 放模块顶层state 单例定义后)执行一次,而非 `useAiStore()` 内部。
- **原因**`state` 是模块级单例,`useAiStore()` 被 App.vue/AiChat.vue/AiDetached.vue 多处调用——放内部会重复执行恢复、覆盖用户当次操作。模块级只跑一次才对。
- **状态**:✅ Sprint 10
> 注:窗口位置/大小用 tauri-plugin-window-state、detached/docked 不持久化两条**已在主文档保留**(属设计决策)。
---
## 九、i18n实现细节
### i18n 模块必须命名空间化导出
> 已移入 [经验记录-2026-06-14.md](./经验记录-2026-06-14.md)「踩坑」分组。
### 状态枚举 i18nconstants 存 keyview 包 $t
- **决策**`constants/project.ts``PROJECT_STATUS_LABELS`/`TASK_STATUS_LABELS` 值从中文文案改存 i18n key`planning: 'projects.status.planning'``projectStatusLabel`/`taskStatusLabel` 返回 keyview 显示处包 `$t(projectStatusLabel(x))``PRIORITY_LABELS`P0/P1是代号非文案不动。
- **原因**constants 是纯数据/结构层,不应含展示文案(违反分层);文案归 localeconstants 管映射结构。
- **状态**:✅ 已落地
---
**相关文档**
- [功能决策记录](./功能决策记录-2026-06-14.md) — 需求规格 + 设计决策规格(当前真相源)
- [经验记录](./经验记录-2026-06-14.md) — 踩坑/约定/技巧/bug 排查教训

View File

@@ -0,0 +1,150 @@
# 功能创意池 — 2026-06-14
> 性质: **创意池 / 待评估**(非已定决策。评估通过后才转正式 concept 文档 + 进 todo)
> 关联: df-ideas · df-workflow · df-ai · Decision 实体 · 规格契约自检机制
> 生成背景: 基于当前系统做功能架构创意,尽量避开已构想范围(创意 4/5 与规格契约自检为延伸关系,见各创意独创列)
> 修订: 2026-06-14 自审后修订 —— 可行性论证诚实化(复用/新造分清)、创意 4/5 重定位为规格契约自检延伸、首选从 4 改为 1、每创意补「难点与风险 + 失败模式」
---
## 一、怎么用这个池子
- 每个创意 = 一个**候选功能方向**,非已定决策
- 状态流转: `💡待评估``📐待设计`(立 concept 文档) → `🔨待实施`(进 todo)
- 评估维度: 新颖性 / 实用价值 / 可行性 / 与现有构想的差异度
- 转正流程: 评估通过 → 单独立 `<主题>-concept.md`(参考 `任务推进构想-2026-06-14.md` 风格)→ 进 todo
## 二、已避开的方向(防重复提案)
| 已有/已构想方向 | 落点 |
|---|---|
| AI-First 任务推进链(三闸门) | `任务推进构想-2026-06-14.md` 已设计 |
| 对抗式想法评估(三路论证) | df-ideas 启发式版已实现 |
| 工作流审批子系统 | Wave5 已完成 |
| 对话内异步审批 | `aichat异步审批构想-2026-06-14.md` |
| 信息密度优化(折叠) | `aichat信息密度构想-2026-06-14.md` |
| 模型能力系统/路由 | todo `F-260614-01` |
| 数据变更联动刷新 | todo `AR-11` |
| 创作模板系统 | todo 已构想 |
| 知识库 MCP Server | todo `F-260614-10` |
| **规格契约自检(活契约+AI自检)** | `规格契约自检机制-2026-06-14.md`。⚠️ **创意 4/5 是其延伸(从一致性 → 陈旧度/回归),非完全避开** |
**本池 5 个创意切入的空白维度**: 演化 · 时效 · 回溯 · 债务 · 契约
---
## 三、创意清单
> 每个创意含: 核心表格(可行性列区分「复用」与「新造」)+ ⚠️ 难点与风险 + 💀 失败模式
### 1. 想法演化图谱(Idea Genealogy Graph) 💡待评估 ⭐首选评估
| 维度 | 内容 |
|---|---|
| **核心** | 给想法做版本控制: 分裂(A→A1/A2 变体)、合并(B+C→D)、演化(A1→A2 迭代)。形成有向演化树,而非当前扁平列表。注: `related_ideas` 字段是否真闲置待核实 |
| **独创** | **中**。把想法当"活体"追踪血统,区别于笔记/看板的扁平存储。但"分裂/合并"本质是版本控制 + 关系图(Git 已是此模型),套到想法上是应用层创新,非机制创新 |
| **价值** | 独立开发者痛点: 想法散落、重复想同一件事而不自知。演化图能回答"这想法三年前想过、演变成啥、为何没做" |
| **可行性** | **复用**: Idea 实体加 `parent_id / derived_from[] / merged_into` 三字段 + 前端复用 df-workflow DAG 可视化 + 评分引擎增量重算子树。**新造**: 关系建立机制(见难点,是真正成本所在) |
| **落地关键** | 关系字段建模 + 演化树渲染 + 分裂/合并交互 |
**⚠️ 难点与风险**
- **关系谁来建是核心漏洞**: 手动建 → 用户不知两想法相关,图谱永远稀疏;AI 辅助建 → 需语义相似度检索(当前 df-ideas 无此能力),不是"加三字段"那么轻
- 演化树布局算法非平凡(多层 DAG 节点排布,复用 DAG 可视化但树 ≠ 工作流 DAG)
- 关系正确性: 错误的合并/分裂会污染反推的产品方向
**💀 失败模式**: 图谱稀疏(没人建关系)→ 沦为摆设;或 AI 误判相似度 → 错误合并污染演化树。
### 2. 灵感孵化器(Idea Incubator) 💡待评估
| 维度 | 内容 |
|---|---|
| **核心** | 评估为 `Defer` / 低分的想法进"孵化器"。三类事件触发自动再评估: ①新技术栈匹配能力;②新想法建立演化关联;③固定周期(如 30 天)。时机成熟浮出提醒 |
| **独创** | **中**。承认想法有时效性,把评估从"快照"变"持续监听"。但"延迟队列 + 条件触发"是通用模式,挂到想法上是场景应用 |
| **价值** | 好想法死于"现在不是时候"后被遗忘。背景静默复检,贴合产研节奏 |
| **可行性** | **复用**: `adversarial.rs` 评估管线(已预留 LLM 接口)。**新造**: `incubator` 表 + 后台 ticker + 触发条件 DSL。**注意**: 评估当前手动触发(晋升走前端),后台自动重评估触及触发机制,**非纯增量** |
| **落地关键** | 触发条件 DSL + 后台 ticker + 浮出提醒 |
**⚠️ 难点与风险**
- 评估从手动 → 自动触及核心链路(触发机制改造),不是"纯增量不碰核心"
- 触发条件①"新技术栈匹配"要求 Idea 能表达"我需要什么技术栈",当前 Idea 实体无此字段 —— 可行性有前置漏洞
- **依赖创意 1**: 触发器②"演化关联"依赖创意 1 的演化关系先存在(见第四节依赖)
**💀 失败模式**: 触发条件太宽 → 频繁打扰变垃圾提醒;太窄 → 永不触发,等同丢弃。
### 3. 执行回放时间线(Execution Replay) 💡待评估
| 维度 | 内容 |
|---|---|
| **核心** | 工作流执行 + Git commit + AI 决策录制为不可变时间线。可回退到任意历史节点,隔离环境改参数重放分支(what-if),对比结果差异 |
| **独创** | **中**。可回放工作流是成熟模式(temporal/cadence 的 event sourcing + replay)。**独创点应收敛到"决策级 what-if 回放"**(面向产研决策维度的假设检验),工作流回放只是载体,非独创本身 |
| **价值** | 回答"如果当时审批选了另一分支会怎样""AI 节点为何这么决策"。对复盘、调试失败工作流价值高 |
| **可行性** | **复用**: `df-execute` Docker 隔离 + `df-workflow` DAG / node IO 结构化。**新造**: 节点 IO 快照持久化 + 状态机重放 + 副作用处理。**注意**: Docker 隔离 ≠ 可重放,隔离只解决执行环境,快照/重放是新工作量 |
| **落地关键** | 节点 IO 快照持久化 + 隔离环境重放 + diff 对比视图 |
**⚠️ 难点与风险**
- "架构已铺好差最后一公里"高估 —— 最后一公里(快照序列化 + 状态机重放 + 副作用处理)可能是最难的
- 副作用处理: 有外部副作用的节点(写文件/调 API)无法纯重放,需标记 + mock
- 存储成本: 全程录制 IO,长期项目存储膨胀
**💀 失败模式**: 副作用节点无法重放 → what-if 结果失真;或快照存储膨胀 → 用户关闭录制。
### 4. 上下文债务追踪(Context Debt Tracker) 💡待评估
| 维度 | 内容 |
|---|---|
| **核心** | AI 定期审计项目"隐式债务": ①引用已删除文件的决策;②"暂时如此"从未回头的 TODO;③前提假设失效;④重复实现/废弃路径。生成债务报告 + 偿还优先级 |
| **独创** | **中高(重定位)**: 本创意是 [规格契约自检机制](规格契约自检机制-2026-06-14.md)的**延伸** —— 从"AI 自检决策规格是否被遵守(一致性)"扩展到"AI 自检决策是否腐化(陈旧度)"。非全新方向,是规格契约自检的第二个应用维度 |
| **价值** | 长期项目头号隐性成本是"上下文腐化"。让 AI 当项目审计师。对维护多项目尤其救命 |
| **可行性** | **复用**: `df-project` Git/路径扫描 + Decision 实体 + `df-ai` 路由 + df-nodes agent 抽象。**新造**: 债务探测器 agent + 偿还优先级算法。**注意**: agent 跨文件推理判断"前提失效"可靠性存疑,"纯 agent 编排"低估了质量风险 |
| **落地关键** | 债务类型分类 + 探测 agent + **偿还优先级算法(未定义,核心难点)** |
**⚠️ 难点与风险**
- **依赖多项目基础(未就绪)**: 价值高度依赖"一人维护多项目"场景,而 devflow 当前单项目/本地优先,导入历史项目(todo)未做。**时序倒置**
- agent 跨文件推理可靠性: 误判"前提失效"会误报,误报泛滥 → 用户关闭
- 偿还优先级算法无现成方案,需自设计
**💀 失败模式**: agent 误报泛滥 → 用户关闭功能;或单项目场景下债务量不足以体现价值。
### 5. 决策回归守护(Decision Regression Guard) 💡待评估
| 维度 | 内容 |
|---|---|
| **核心** | 关键决策绑定可执行验证契约("选 A 因性能优" → 绑性能基准脚本)。代码变更触及相关模块(Git diff 关联)时守护自动重跑;契约失败 = 决策失效,阻断发布或报警 |
| **独创** | **中(降级)**: 实现机制等同测试(跑脚本验证断言),差异仅在断言语义来源(决策 vs 需求)。准确定位为"**决策驱动的测试生成**" —— 独创点在从决策自动生成守护测试,非守护机制本身 |
| **价值** | 解决"决策当初对、后来悄悄变错"的隐患 |
| **可行性** | **复用**: `df-workflow` DAG + `df-execute` 脚本执行 + Decision 实体 + Git diff。**新造**: 契约绑定语法 + 守护触发节点。**注意**: 审批(事前)与守护(事后持续)机制不同,Wave5 审批基座未必直接复用为持续触发 |
| **落地关键** | 契约绑定语法 + Git diff 关联触发 + 失效阻断策略 |
**⚠️ 难点与风险**
- **与创意 4 争抢 Decision 实体扩展**: 4 要腐化审计标记,5 要 `guard_contract` 字段。若都做需先定义 Decision 统一扩展模型
- 从决策文本自动生成有意义的守护测试,LLM 生成质量是瓶颈
- "代码变更触及相关模块"的关联判定(Git diff → Decision)需决策到代码的映射,无现成方案
**💀 失败模式**: LLM 生成的守护测试无意义/假通过 → 守护形同虚设;或关联判定不准 → 该触发没触发。
---
## 四、优先级建议
| 创意 | 落地难度 | 独创性 | 依赖 | 建议 |
|---|---|---|---|---|
| 1 想法演化图谱 | 低 | 中 | 无 | **⭐首选评估**: 改动最局限(df-ideas)、依赖最少,但先解"关系谁来建"漏洞 |
| 2 灵感孵化器 | 中 | 中 | 依赖 1 的演化关系字段 | 与 1 有依赖非并行;独立做需砍"演化关联"触发器 |
| 5 决策回归守护 | 中 | 中 | 与 4 争 Decision 扩展 | 决策驱动测试生成,待 LLM 生成质量成熟 |
| 4 上下文债务追踪 | 中高 | 中高 | 依赖多项目基础(未就绪) | 价值高但**时序靠后** —— 等"导入历史项目"做完 |
| 3 执行回放 | 高 | 中 | 副作用处理/存储成本 | 工作量最大,后置;独创点重定位为决策级回放 |
**首选变更说明**: 原⭐推荐创意 4,自审后发现三理由均打折(痛点依赖多项目/无需新基建被高估/差异化因与规格契约自检重叠而削弱),且依赖未就绪的"导入历史项目"基础,**降级**。首选改**创意 1** —— 改动最局限、依赖最少,但须先解决「演化关系建立机制」核心漏洞。
---
## 五、后期跟进入口
1. 选一个创意(**首选 1 演化图谱**,先解关系建立机制;4 债务追踪价值最高,但等"导入历史项目"基础就绪)
2. 走 devflow 自身评估链: df-ideas 对抗评估(启发式 → LLM)
3. 评估通过 → 立 `<主题>-concept.md` → 进 todo
4. 本池对应创意状态改为 `📐待设计`
---
**相关**: [想法探索-对抗式评估](../03-模块文档/想法探索-对抗式评估-2026-06-12.md) · [任务推进构想](任务推进构想-2026-06-14.md) · [规格契约自检机制](规格契约自检机制-2026-06-14.md)

View File

@@ -0,0 +1,195 @@
# 文档记录规范
> 写文档 / 更新文档时的**路由规则**:记到哪、优先写哪、怎么避免重复。
> 创建:2026-06-12 | 维护:文档结构变化时同步
---
## 一、核心原则
1. **单一真相源(SSOT)**:每类信息只在一个主文档展开,别同一内容抄多处。
2. **不复制,只引用**:他处需要时加 `[详情](链接)`,不抄正文。
3. **决策与流水分离**:决策记「为什么这么定」,流水记「做了啥」。别混。
4. **先主后辅**:同一变更涉及多处 → 先写真相源,再在引用处加链接。
5. **决策记录范围收紧 — 只记人定事实,不记大模型分析结论**(2026-06-14,📐 基准原则):
- 决策记录**只记**与大模型能力无关的人定事实 — 业务需求规格 / 人定技术选型 / 人的设计取舍。
- **不记**大模型分析/推断结论(性能瓶颈 / 根因 / 最优架构 / 排查结果) — 这些按需让当时的模型即时产出,不沉淀。
- 原因:大模型分析结论受当前模型能力天花板约束,模型逐月变强,今天的「最优分析」明天会被更强模型超越 → 记录过时;沉淀 = 固化次优解,阻碍未来用更强模型即时得出更优解。即使埋点/实测「验证」了,也不改其受能力天花板约束、会随模型升级被超越的本质。
---
## 二、文档职责矩阵(真相源)
| 文档 | 唯一职责(记什么) | 不记什么 |
|---|---|---|
| `PROGRESS.md`(根级) | 工作流水:Sprint 做了啥 / 遗留 / 下一步 | 决策原因、实现细节、需求规格 |
| `ARCHITECTURE.md` | 系统架构全貌:crate 结构 / 数据模型 / Phase 规划 | 功能点取舍、Sprint 流水 |
| `02-架构设计/滚动规范/Phase1架构决策-2026-06-12.md` | 架构级选型(ADR,系统级) | 功能实现层取舍 |
| `02-架构设计/滚动规范/功能决策记录-2026-06-14.md` | 功能**需求规格 + 设计决策规格**(为什么这么定 + 要做什么) | 流水、架构级选型、经验性内容 |
| `02-架构设计/滚动规范/经验记录-2026-06-14.md` | 经验性内容(踩坑/约定/技巧/bug 排查教训) | 决策、需求、流水 |
| `02-架构设计/滚动规范/功能决策记录-归档-2026-06-14.md` | 纯流水/老 Sprint/UX 微调/已被取代(归档只读) | (不再维护更新) |
| `03-模块文档/*.md` | 各 crate 实现细节(单模块内) | 跨模块决策、流水 |
| `04-功能迭代/DEVFLOW-N.*.md` | 功能开发过程记录(一次性,开发期) | 持续维护的决策 |
| `05-代码审查/*.md` | 审查报告与发现 | (若成决策 → 转记功能决策记录) |
| `06-前端开发/*.md` | 前端规范 / 迁移指南 | 后端实现 |
| `07-项目管理/*.md` | Phase 计划 / 任务清单 | 实现流水(那是 PROGRESS) |
| `08-用户指南/*.md` | 用户手册 / 配置 / FAQ | 内部实现细节 |
| `01-技术文档/*.md` | 技术专题研究(CRUD 模式、IPC 模式) | 业务功能 |
---
## 三、内容路由表(写东西先查这个)
| 你要记的内容 | 主文档(优先写) | 按需交叉引用 |
|---|---|---|
| 本 Sprint 做了啥 / 遗留 | `PROGRESS.md` | `04-功能迭代/`(详过程) |
| 为什么这么实现(选 A 不选 B) | `功能决策记录-2026-06-14.md` | 模块文档、PROGRESS |
| 踩坑 / 约定 / 技巧 / bug 排查教训 | `经验记录-2026-06-14.md` | 功能决策记录 |
| 架构级选型 | `Phase1架构决策-2026-06-12.md` / `ARCHITECTURE.md` | — |
| 新需求 / 待办 / 功能规格 | `功能决策记录-2026-06-14.md`(需求维度) | `Phase2计划-2026-06-12.md` |
| 对话中需求澄清(原以为 X 实为 Y) | `功能决策记录-2026-06-14.md`(需求澄清) | — |
| 老 Sprint 决策 / UX 微调 / 已被取代 | `功能决策记录-归档-2026-06-14.md` | (归档只读,不再维护) |
| 单模块实现细节 | `03-模块文档/<对应>.md` | — |
| 代码审查发现 | `05-代码审查/` | 转决策 → `功能决策记录-2026-06-14.md` |
| 前端规范变更 | `06-前端开发/` | — |
| 用户操作说明 | `08-用户指南/` | — |
---
## 四、更新顺序(同一变更涉及多处)
1. **真相源先写完整**(按路由表的主文档)
2. **PROGRESS 记一笔 + 链接**(流水 + 指向详情)
3. **引用处加交叉链接**,不抄正文
**例**:做了「shouldKeepOpen 折叠」
- 真相源:`功能决策记录-2026-06-14.md` 写决策 / 原因 / 状态 ✅
- 流水:`PROGRESS.md` 记「审查①已落地」+ 链接到功能决策记录
- **不**在模块文档 / ARCHITECTURE 重复抄决策正文
---
## 五、唯一性记录与检测(防散乱)
**核心要求:每类信息一个主文档,不散乱、不重复。** 文档治理底线。
### 唯一真相源
见「二、文档职责矩阵」——每类信息的唯一主文档。
### 检测方法
**1. 记前查重(每次记录时)**
- 记决策/需求前,先 grep 查该点是否已存在:
```bash
grep -rl "<关键词>" docs/ PROGRESS.md ARCHITECTURE.md
```
- 已存在 → 更新原条,不新增(见 decision-record skill「维护:查重」)
**2. 交叉引用单向(禁双向复制)**
- 主文档(真相源)展开内容,引用方只放 `[详情](链接)`
- ❌ A 写决策正文,B 又抄一遍 → ✅ B 只链接 A
**3. 配合交接文档(PROGRESS)**
- PROGRESS 是**交接文档**,只记「做了啥 + 链接」,不展开决策/需求正文
- 决策正文 → `功能决策记录-2026-06-14.md`;需求 → `功能决策记录` 的「需求与待办」
- 交接路径:读 PROGRESS 知进度 → 读 `功能决策记录` 知「为什么 + 要做什么」→ 读模块文档知「怎么实现」
**4. 定期唯一性扫描(防积累散乱)**
- 时机:文档结构变化 / 新增文档 / 每个 Sprint 末
- 方法:对关键决策点跨文档 grep,确认只在主文档展开
```bash
grep -rl "shouldKeepOpen\|connect_timeout" docs/ PROGRESS.md
```
- 发现散乱 → 合并到主文档,他处改链接
### 不散乱红线
- 同一决策**不**同时进 `功能决策记录` 和 `Phase1架构决策`(功能层 vs 架构层二选一)
- 同一需求**不**同时在 `功能决策记录·需求与待办` 和 `Phase2计划` 展开(一处为主,一处链接)
- PROGRESS**不**抄决策正文,只记「做了 + 链接」
---
## 六、与 decision-record skill 的关系
`decision-record` skill 触发时,按本规范路由:
- **决策 / 需求** → `功能决策记录-2026-06-14.md`(主,真相源)
- skill 执行后 → `PROGRESS.md` 加一笔流水 + 链接(可选,重大决策才加)
Stop hook 触发 skill 时同理,不另立记录位置。
---
## 七、治理体系实现决策(hook 设计)
本规范 + `decision-record` skill + 降频 Stop hook 构成文档治理体系。hook 设计取舍:
### 降频 Stop hook(替代 PreCompact / 每轮自检)
- **决策**:用 `~/.claude/hooks/dr-check.sh`(settings.json 配 Stop hook)按阈值注入自检提示,触发 `decision-record`。
- **原因**:PreCompact hook **只读输入、无法注入 prompt** 触发 skill;Stop hook 支持 `additionalContext` 注入。每轮 Stop 自检消耗大且打断;降频用纯脚本计数**无 API**,省 ~90% token。
- **状态**:✅ 2026-06-12
### 触发阈值:≥20 轮 或 (≥2 轮 且 ≥20 分钟)
- **决策**:累计 ≥20 轮,或 (>1 轮 且 距上次 ≥20 分钟) 才触发;首次运行静默初始化(计时,本轮不触发)。
- **原因**:10 轮约一个功能点推进周期;10 分钟兜底防长对话漏记;兼顾及时与不打扰。
- → 2026-06-14 阈值翻倍(10→20 轮 / 10→20 分钟)。原阈值触发过频,多数自检轮次无实质决策;翻倍减半打扰。✅ 落地(dr-check.sh)
### 防循环:stop_hook_active guard
- **决策**:hook 检测输入 `stop_hook_active=true` → 直接 `exit 0` 放行。
- **原因**:Stop hook 注入 additionalContext 会触发主 Claude 继续 → 再次 Stop → 无限循环;guard 放行第二轮(因 hook 继续的)。
- **状态**:✅
### 状态按项目隔离
- **决策**:轮次计数 + 上次触发时间戳存 `~/.claude/.dr-state/<项目key>.rounds|.last`(键由路径转义),不落项目目录。
- **原因**:多项目独立计数不串;不污染 git 仓库。
- **状态**:✅
### 决策记录执行子代理化(2026-06-14)
- **决策**:stop hook 自检触发后,记录动作 spawn 后台子代理执行(项目 `decision-recorder` 子代理,位于 `devflow/.claude/agents/`),主代理不亲自 grep/写文档。
- **原因**:避免记录动作(grep/读写文档)污染主对话上下文、打断主流程;记录规范固化进子代理 system prompt,主代理只传决策内容。
- **状态**:✅ 落地(dr-check.sh 的 CTX 已改为指令 spawn 子代理)
---
## 八、文档命名规范(2026-06-19 确立)
> 本节确立 `docs/02-架构设计/` 及同类设计文档目录的命名格式,便于检索 / 排序 / 关联跟踪项。规范为**对现状的归纳**,不强制回溯重命名(重命名引用代价大,见 §十一 riskNote)。
### 格式
统一为 `<前缀>-<主题>-<日期>.md`,前缀按文档性质分三类:
| 类别 | 前缀 | 格式 | 示例 |
|---|---|---|---|
| 功能 / bug / review 方案 | 功能编号(`F-NN` / `B-NN` / `CR-NN`)或 bug ID(`B-YYMMDD-NN`) | `<编号>-<主题>-<日期>.md` | `F-09-多会话并发架构设计-2026-06-19.md` / `B-03-人工审批响应机制-2026-06-14.md` |
| 滚动维护文档(跨 Sprint 持续追加) | 无编号 | `<主题>-<日期>.md` | `功能决策记录-2026-06-14.md` / `经验记录-2026-06-14.md` |
| 专题设计 / 构想 / 审查报告 | 主题(含领域前缀如 `aichat` / `工作流` / `任务推进`) | `<主题>-<日期>.md` | `aichat审查报告-2026-06-14.md` / `工作流脚本执行边界-2026-06-15.md` |
### 约定
1. **日期 = 创建日期**(YYYY-MM-DD),不随更新变(更新在正文「实施状态」块体现,不改文件名日期)。
2. **前缀 = 跟踪项编号**时,与 `todo.md` / `待审查.md` 的编号一一对应,便于双向定位。
3. **被取代 / 过时文档不删**:文件顶部加 `## 实施状态` 块或 `> 过时` 标注,索引「状态」列标 🗄。例:`F-09B-多会话并发设计-2026-06-16.md` 被 `F-09-...-2026-06-19.md` 取代,旧版保留回溯。
4. **同主题多版本**:新版用相同主编号 + 不同日期(`F-09` 与 `F-09B` 为历史命名,新设计统一用主编号 + 日期,不再加 `B` 后缀)。
### 检索
按性质四类查阅,见本目录 [INDEX.md](../INDEX.md):
- 滚动规范与决策记录 / 已编号方案 / 专项设计 / 构想与审查
---
## 九、待修(文档不一致)
- ~~`docs/INDEX.md` 在 `07-项目管理/` 树下登记了 `PROGRESS.md`,但实际 PROGRESS 只在根级,`07-项目管理/` 下无此文件~~ → ✅ 已修(2026-06-12):移除该行,PROGRESS 统一指向根级。
---
**相关文档**:
- `docs/INDEX.md` — 文档导航
- `docs/02-架构设计/滚动规范/功能决策记录-2026-06-14.md` — 需求规格 + 设计决策规格(本规范的主要应用对象)
- `docs/02-架构设计/滚动规范/经验记录-2026-06-14.md` — 踩坑/约定/技巧/bug 排查教训
- `docs/02-架构设计/滚动规范/功能决策记录-归档-2026-06-14.md` — 归档只读(纯流水/老 Sprint/UX 微调)
- `PROGRESS.md` — 工作流水

View File

@@ -0,0 +1,140 @@
# 经验记录
> DevFlow 开发中沉淀的**经验性内容**——踩坑、约定、技巧、bug 排查教训。聚焦「这个坑怎么踩的 / 这个约定为什么这么定 / 这个 bug 怎么定位的」,区别于 [功能决策记录](./功能决策记录-2026-06-14.md)(记需求规格 + 设计决策规格)。
>
> 创建2026-06-14从功能决策记录-2026-06-14.md 分流出经验性条目) | 维护:随开发追加
## 约定
- 按类型分组:**踩坑**(隐性坑/反直觉)/ **约定**(代码实现约定 / 命名约定)/ **技巧**(具体技巧/配置)/ **bug 排查**bug 定位过程与教训)。
- 每条标题标 `[来源日期]` + `[Sprint]`(如有),便于回溯原上下文。
- 三要素:**现象/决策** → **原因/根因****状态/教训**
- 与功能决策记录区分:这里记「怎么实现的细节坑」,不记「为什么这么设计」。
---
## 一、踩坑
### i18n 模块必须命名空间化导出(扁平导出会断 $t + 键覆盖)[2026-06-14]
- **现象**:左侧菜单显示 `'nav.tasks'`(原样键名);`dashboard` 整页显示 key 名;`AiChat``$t('ai.assistant')` 失效。
- **决策**:每个 i18n 模块文件 `export default { 命名空间: {...} }`(如 `nav.ts``{ nav: {...} }`**禁止扁平导出顶层词条**。模板查询走 `$t('命名空间.key')`
- **根因**`index.ts` 聚合是 `Object.assign` 扁平合并各模块顶层 key见「locale 拆分 + glob 聚合」决策)。扁平导出导致两个 bug`$t('nav.tasks')``messages.nav` 不存在,原样显示键名;② 扁平键(如 nav 的 `ideas`/`projects`/`tasks`/`knowledge`与同名命名空间模块ideas.ts/projects.ts/...)按文件名字母序互相覆盖。本次 nav.ts 扁平导出导致 4 个键被覆盖。
- **状态/教训**:✅ 系统性修复,共 4 模块扁平已全部改嵌套zh/en 8 文件nav / common8 文件 25 处 $t 引用)/ dashboard / ai。原本正确嵌套ideas/projects/tasks/settings/knowledge/projectDetail/aiChat。**教训**:扁平导出是体系性 bug 非单点。排查「$t 显示原样键名」时应**优先怀疑模块导出结构(扁平 vs 嵌套)**,而非 SSR / locale 初始化。
- **绕路纠错**:曾误判根因为 SSR实际 Tauri 纯客户端无 SSR→ nav 走 `getNavTranslations` 硬编码 map + displayText 绕路 → 清除绕路恢复标准 `$t`
---
## 二、约定
### Anthropic 流式 output_tokens 当累计值直接覆盖 / 流式 token 落库走累加模式 [2026-06-13]
- **决策**:① `message_delta` 事件的 output_tokens 直接覆盖 completion_tokens**不像 prompt 那样累加**;② `save_conversation` upsert 路径 token 读旧值叠加(非覆盖);`run_agentic_loop` 局部累加器每轮叠加、退出时一次性传 save。
- **原因**:① Anthropic 协议在 `message_delta` 返回的是**累计** output_tokens截至当前总量非增量当增量处理会重复计算。OpenAI 则是末 chunk 一次性给全量——两协议语义不同,各自处理。② 审批暂停→恢复 spawn 全新 `run_agentic_loop` 实例,新 loop 局部累加器从 0 起;若覆盖写会丢旧 loop 已落库的 token。累加保证跨 loop 实例的对话总用量正确。
- **状态**:✅ 2026-06-13两协议各自语义处理 + 跨 loop 累加保对话总量)
### db 字段加列须同步四处migration + crud 白名单 + AI 工具层白名单 + 工具描述 [2026-06-14]
- **现象**AI 对话让 AI 绑定目录update_project(path) 报「不允许更新字段 'path'」,但 db schema 和 crud 白名单都已有 path。
- **根因**可更新字段有两套独立白名单——crud.rs::allowed_columns_forDB 层)+ ai.rs 工具闭包硬编码 matchAI 工具层)。加 path/stack 时只同步 DB 层漏 AI 工具层,两层不一致。
- **教训**:加 Record 可变字段同步四处migration + crud 白名单 + ai.rs 工具白名单 + 工具描述。排查「DB 有字段但工具报不允许」直查 ai.rs 硬编码。架构债:白名单双份去重。
### Tauri 命令文件拆子 module命令函数必须 glob `pub use *`,不能逐个显式 [2026-06-14]
- **现象**:把含 `#[tauri::command]` 的单文件(如 ai.rs拆成 `ai/` 子 module 时mod.rs 用 `pub use self::commands::{ai_chat_send, ...}` 逐个显式重导出 17 个命令,`cargo check` 报 40 个 E0433`cannot find __cmd__ai_chat_send in ai` / `cannot find __tauri_command_name_ai_chat_send in ai`
- **根因**`#[tauri::command]` 宏不只生成命令函数本身,还用 `paste!` 宏拼接生成一组同模块定义的内部符号(`__cmd__xxx``__tauri_command_name_xxx`)。`generate_handler!` 解析 `commands::ai::ai_chat_send` 时会查找 `commands::ai::__cmd__ai_chat_send`。逐个 `pub use self::commands::{ai_chat_send}` 只拉函数本身,**拉不到这些 `__cmd__` 内部符号**(即使它们在原模块是 pub 的)。
- **教训**拆命令文件时mod.rs 重导出命令必须用 `pub use self::commands::*;`glob 把宏生成的全部符号一起拉到上层路径),不能用逐个显式。非命令 pub 项(如 `build_ai_tool_registry`/`restore_pending_approvals`)可逐个显式。后续若拆 idea.rs/project.rs/task.rs 等其他含命令的大文件,同此模式。
- **状态**:✅ 2026-06-14 验证ai.rs 拆 11 子 moduleglob 重导出后 cargo check 0 error
### 跨层模块拆分:`super::xxx` 路径失效需改全限定 [2026-06-14]
- **现象**ai.rscommands 直接子模块)拆到 `ai/xxx.rs`commands 孙模块6 个子文件 `use super::now_millis` 全报 E0425 unresolved import。
- **根因**`super` 指向当前模块的父——ai.rs 时 `super` = `commands``now_millis` 定义处);拆到 `ai/xxx.rs``super` = `commands::ai``now_millis` 在祖父模块 `commands`
- **教训**:拆层后所有 `super::xxx` 引用需重审。父模块的 helper`now_millis`)改全限定 `crate::commands::now_millis` 最稳(不依赖层级)。或拆层前把 helper 下沉到子 mod.rs 内 `use` 一次,子文件用 `super::xxx`
- **状态**:✅ 2026-06-14 验证(批量改 `crate::commands::now_millis`6 文件 20+ 处)
### 删文件后被 linter/工具重建为 0 字节触发 E0761 [2026-06-14]
- **现象**`rm commands/ai.rs` 后某 linter/hook 又建了 0 字节的 ai.rs触发 `E0761: file for module ai found at both ai.rs and ai/mod.rs`,且 Rust 优先选空文件导致后续 40 个 `cannot find __cmd__xxx`(与 glob 重导出坑叠加,表象一致根因不同)。
- **教训**:拆分时删原文件后**立即 ls 验证不存在**再跑 cargo check避免空文件 + 目录并存的 E0761 与命令宏符号坑混淆。E0761 出现先查是否有 0 字节残留文件。
- **状态**:✅ 2026-06-14 验证(删空 ai.rs 后通过)
### ALLOWED_COLUMNS 从全局共享演进为按表隔离 [2026-06-13]
- **决策**`crud.rs` 列名白名单从单一全局 `ALLOWED_COLUMNS` 改为 `allowed_columns_for(table)` 按表 match`validate_column_name(field, table)` 接收表名;宏 `query`/`update_field``$table`。专用更新路径列knowledges.embedding 走 set_embedding、projects.deleted_at 走 soft_delete/restore排除出白名单。
- **演进原因**:原原则(见功能决策记录需求澄清「代码审查甄别原则」)基于「全局白名单够防注入」。本轮多代理代码审查发现**真实 bug**:全局白名单**误含 ideas 表没有的 `reasoning` 列**reasoning 属 knowledges/V10`update_idea("reasoning")` 会 validate 通过但 SQLite 报 `no such column`——错误从「白名单拒绝」退化成「底层 SQL 错」且语义错。按表隔离既修此 bugideas 白名单不含 reasoning又防未来跨表字段update_task 误传 projects 的 `name` 在校验阶段拒绝,非靠 SQL 兜底)。
- **代价/取舍**12 表 × N 列的 match 冗长,但数据驱动、可读、一次写对。**规模判断不变**(仍不加分页/不拆 LIMIT仅白名单从「全局防注入」升级为「按表防注入 + 防跨表字段」。
- **状态**:✅ 2026-06-13 落地cargo check + df-storage 32 test 全绿,含 `update_field_rejects_cross_table_column_tasks_name` 用例验证跨表字段被拒)
### 配置存储SQLite/AppState Arc<Mutex>,非 Tauri app config [2026-06-13]
- **决策**KnowledgeConfig提取+注入共 5 项)**存 AppState 内存**`knowledge_config: Arc<Mutex<KnowledgeConfig>>`),前后端通过 `knowledge_get_config`/`knowledge_save_config` IPC 读写;**不引入 tauri-plugin-store**。
- **演进**[2026-06-13 初版设计] 写「存 Tauri app config」 → [2026-06-13 审查修正] 代码实证项目 Cargo.toml 仅 opener+window_state 两插件,**从未用过 config/store 机制**现有设置走两条路SQLite 存 provider / localStorage 存 UI 偏好) → [2026-06-14] **`SettingsRepo` 兑现本条预言**`app_settings` KV 表V13 迁移)+ 手写 `SettingsRepo`(get/set/get_all/delete不走 `impl_repo!` 宏因 KV 无固定 schema)。localStorage 11 key 迁移启动:敏感 `df-connections` + UI 偏好(theme/language/ai-width/ai-ui/token/concurrency) + `df-ai-active-conv`;例外 `df-ai-gen`/`df-ai-text`(流式临时快照,每个 delta 写一次SQLite 高频写拖慢流式,留 localStorage
- **原因**AI Provider 配置已是 SQLite+Repo+IPC 模式,知识库行为配置(后端行为,非 UI 偏好)对齐同模式最一致。引入 tauri-plugin-store 是全新基础设施依赖,与既有 DB 路线割裂。AppState Arc<Mutex> 内存持有 + IPC 读写,启动时 `default()` 初始化Tier 1 未持久化到 DB进程重启回默认——够用因这是行为偏好非数据。未来要持久化时复用同一套 SettingsRepo 即可。
- **状态**:✅ 已实施Tier 1
### 知识删除语义knowledge_archive 软删除(命名统一)[2026-06-13]
- **决策**:知识删除 command 命名 `knowledge_archive`(执行 `UPDATE status='archived'`**不叫 knowledge_delete**。匹配 `ai_conversation_archive` 先例;主列表 `knowledge_list(status=None)` 默认仅返回 publishedlibrary 纯已发布,[2026-06-16] F-260616-02 决策 a 收窄pending_review 归 `knowledge_list_candidates` 收件箱,不再混杂 library
- **原因/取舍**idea/task/project 的 `delete_xxx` 都是硬删DELETE FROM若 knowledge 也叫 delete 却做归档API 语义混淆(调用方期望数据消失,实际还在 DB。conversation 模块已有正确先例archive 命名表示软删除)。软删除复用 archived 状态,数据保留可追溯,列表默认过滤保证用户感知「已删除」。状态机 published→archived 也走同一路径。
- **状态**:✅ 已实施Tier 1
### Store 状态字段用 getter 替代引用快照 [Sprint 10]
- **决策**`useProjectStore()` 返回对象的状态字段projects/tasks/ideas/workflowExecutions/liveEvents/loading/error改 getter 实时读 state而非 `ideas: state.ideas` 引用快照。
- **原因**:引用快照在 `loadIdeas()` 等重新赋值 state.ideas 后,返回对象的 ideas 属性不更新刷新后视图空需切菜单再切回才显示getter 每次读 state响应链成立。computedstats/pendingApproval在 reactive 内仍自动解包,各视图用法零改动。
- **状态**:🚧 Sprint 10编译/构建通过,未 tauri dev 实测,根因通杀 Projects/Tasks/Dashboard
---
## 三、技巧
### migrate_v4PRAGMA table_info 探测列存在性 [Sprint 10]
- **决策**v4 加 `archived` 列时,用 `PRAGMA table_info` 幂等探测列是否已存在,而非仅依赖 `schema_version` 版本号 gate。
- **原因**:历史坏库 `schema_version` 值混乱(早期迁移异常致版本号与实际 schema 不符),版本号不可靠;直接探列存在性最稳——已存在则跳过,不存在则补建,幂等可重入。
- **状态**:✅ Sprint 10
### Vite 端口 `strictPort: true` 不自动迁移 [Sprint 1]
- **决策**`vite.config.ts``port: 1420` + `strictPort: true`,端口被占时**直接报错退出**而非自动 +1 迁移;`tauri.conf.json``devUrl` 写死 `http://localhost:1420`
- **原因**Tauri webview 启动时按 `devUrl` 加载前端,若 Vite 因冲突静默迁移到 1421 而 devUrl 仍是 1420 → 白屏/连不上,错误难定位(易误判为前端代码 bug`strictPort` 让端口冲突当场炸出定位明确。代价1420 被占需手动杀进程但换取「devUrl 与实际端口必一致」的不变量。
- **状态**:✅ Sprint 1本次会话核对1420 vs 2661 反复折腾后回退到 1420即此耦合的直接体现
---
## 四、bug 排查
### ai_tool_executions 审计回写失效Med/High 审批后卡 pending[#54 实测]
- **现象**:用户审批 Med/High 工具后执行成功(副作用落库,如 create_project→projects 有记录),但 `ai_tool_executions``status=pending / decided_by=None / executed_at=None / result=None`审计未闭环。Low 工具正常(`decided_by=auto` 完整)。
- **根因(代码层定位)**`crud.rs:103``impl_repo!` 生成的通用 `query` 硬编码 `ORDER BY created_at DESC`,但 `ai_tool_executions` 表**无 `created_at` 列** → `audit_finalize``query("tool_call_id", x)` SQL 报 `no such column: created_at``.unwrap_or_default()` 吞错返回空 → `if let Some(rec)` 为 None → **永不回写**。Low 工具不走 query`process_tool_calls` Low 分支直接 `audit_tool_call` insert 完整记录)故不受影响。
- **架构隐患**:通用 `query``ORDER BY created_at` 假设所有表都有该列——`ai_tool_executions`(及潜在其他无 `created_at` 的表)任何 `query()` 调用都静默失败;`unwrap_or_default` 吞 SQL 错误放大隐患。
- **修复**:✅ 已落地2026-06-13。采用方向①`crud.rs``AiToolExecutionRepo` 加专用 `find_by_tool_call_id`(裸 SQL `ORDER BY requested_at DESC LIMIT 1`,绕过宏的 `created_at` 假设);`ai.rs audit_finalize` 改用之,查不到记录改 `tracing::warn`(不再 `unwrap_or_default` 静默吞错)。
- → 未改宏(方向②影响 7+ 表)/ 未加列(方向③需迁移):隐患仅 `ai_tool_executions` 一处暴露,局部修最小影响。
-**架构隐患仍存(未根治)**:通用 `query`/`list_all` 宏对无 `created_at` 的表(`ai_tool_executions`/`node_executions`/`workflow_executions`)调用仍静默失败。当前仅 `ai_tool_executions``query` 调用且已绕开,余者暂无 `query` 调用点。未来新增调用时,要么该表登记 `created_at`,要么宏做容错。
- **教训**:宏生成的通用方法对表 schema 的隐式假设(这里「所有表都有 created_at」是隐蔽的系统性风险`unwrap_or_default()` 吞错误让 bug 隐形——关键路径慎用。
### reasoning 字段回填(修 bugprompt 要求但写库丢弃)[2026-06-13]
- **现象/决策**`KnowledgeRecord``reasoning: Option<String>`V10 ALTER`extract_knowledge_from_conversation` 解析 LLM JSON 的 `reasoning` 字段写入主表;前端详情溯源区展示「🤖 AI 判断依据」。
- **根因**`EXTRACTION_SYSTEM_PROMPT` 早已要求 LLM 输出 `reasoning: "为何值得沉淀"`但提炼循环ai.rs 旧版)只取 kind/title/content/tags/confidence**reasoning 被 LLM 产出却遭代码丢弃**——是信息链断裂的 bug非缺功能。审核员光看 content 结论,缺 AI 判断依据(尤其 confidence=low 的弱信号更靠 reasoning 解释为何还提炼)。回填后溯源完整。
- **状态**:✅ 已实施reasoning 存主表 + extracted 事件 context.reasoning 双写,前端优先取主表降级取事件)
- **教训**LLM 输出字段与代码消费字段须对账——prompt 要求 LLM 产出的字段,代码侧漏消费是常见隐性 bug。
### prompt_tokens=0深挖证伪非代码 bug疑 GLM 订阅端点 message_start 缺 input_tokens[#54 实测发现]
- **现象**`ai_conversations.prompt_tokens=0`completion=1496 正常。GLM-订阅anthropic 协议1 对话 24 消息,所有 assistant 消息 `usage=None`
- **深挖结论(→ 修正初判)**初判「anthropic_compat usage 解析漏 input_tokens待修」**证伪**。逐段验证:
1. `anthropic_compat` message_start 取 `input_tokens→prompt_tokens` **有单测**input=42 过);
2. `stream_llm`857`final_usage=chunk.usage.clone()` 累积对;
3. `ai.rs:699` `tokens.add` 链路对。
代码按标准 Anthropic 协议解析正确。`inp as u32`Some→值None→0completion 有值说明 message_delta 的 output GLM 返回了,**prompt=0 = GLM 订阅端点 message_start 疑未返回 `usage.input_tokens`**(协议非标)。**勿改 anthropic_compat**(改了 = 误改正确实现)。
- **状态**:📐 待修(误判)→ 🚫 非代码 bug。待抓 GLM 订阅 SSE 原文确认 input_tokens 在哪个事件/字段(临时打 message_start/message_delta 的 usage JSON 日志,测完删);若确认端点缺则属 provider 兼容性待办,非解析 bug。
- **教训**bug 定位优先用单测/逐段验证证伪代码层假设,不要急着改「看似正确」的实现。深挖证伪避免了一次误改。
---
**相关文档**
- [功能决策记录](./功能决策记录-2026-06-14.md) — 需求规格 + 设计决策规格
- [功能决策记录-归档](./功能决策记录-归档-2026-06-14.md) — 纯流水 / 老 Sprint / UX 微调 / 已被取代