# 功能决策记录

> 日常开发中对各功能做的**需求规格 + 设计决策规格**（功能粒度，补 [Phase 1 架构决策](./Phase1架构决策-2026-06-12.md) 之下的实现层选择）。聚焦「要做什么 / 为什么这么定」，便于日后回溯。
>
> 创建：2026-06-12 | 范围：Sprint 5–10 | 维护：随开发追加

## 约定

**判断标准**：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（无输出）时最多等 120s（P2 可用 `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 项塞进对话 message（UI 卡 + 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 作真相源 → 致「重启默认丢失」bug：active 是内存态重启清零，而 `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 策略：InitFailed（retryable）耗尽候选后切下一个 provider；Fatal（4xx 非 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 调用点加 router；ai_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` 注入参数，启发式降级为 fallback；F-03 原 A/B/C 选型据此收敛为「全局统一 trait」。
- **状态**：📐 设计未实施（LLM 接入）

### 前后端标签对齐
- **决策**：`recommendation` 全小写空格（后端原 "With Resources" 不匹配前端 map key）；`assessmentClass` 映射到 CSS 类名（`.immediate`/`.soon`/...）。
- **原因**：大小写/类名不一致致中文标签不显示、badge 无色。
- **状态**：✅ Sprint 9

### 评分 0-10（crate）→ 0-100（前端）IPC 缩放 + 多字段写回用单事务 [Sprint 9/10]
- **决策**：① crate 内 IdeaScores 维持 0-10；IPC 层 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/tauri）；commands 层薄封装 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（纯 IO），LLM 调用放 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 TODO，relocate 不并入（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/跳过 M）；LLM 全失败 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 + i18n；scan.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 重复，已标 TODO；commands/project.rs create/relocate 同样）。对称+优雅：绑定是单一子操作，应集中 df-project（normalize_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/Low，AI 自评，**唯一质量指标**）；提炼时另做**查重**（比对 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 1：Chat + 手动录入）

### 对外暴露：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 task），4 个配置项：`auto_extract`（default true）/ `trigger_mode`（on_complete|on_idle|manual_only，default 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 1（Phase 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 不浪费 embed，published 才参与检索）；④ 纯 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-rs（5MB BPE 数据文件对 Tauri 打包不友好）。
- **双层 Semaphore 并发控制**：AppState `LlmConcurrency`——全局并发默认 3 / 单对话默认 2；permit 在 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-13，cargo 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-13（curl 验证 vite 正确展开 glob，vue-tsc PASS）

### 状态枚举 i18n：constants 存 key，view 包 $t
- **决策**：`constants/project.ts` 的 `PROJECT_STATUS_LABELS`/`TASK_STATUS_LABELS` 值从中文文案改存 i18n key（`planning: 'projects.status.planning'`）；`projectStatusLabel`/`taskStatusLabel` 返回 key；view 显示处包 `$t(projectStatusLabel(x))`。`PRIORITY_LABELS`（P0/P1）是代号非文案，不动。
- **原因**：constants 是纯数据/结构层，不应含展示文案（违反分层）；文案归 locale，constants 管映射结构。
- **状态**：✅ 已落地

### 📋 项目 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 占位保 scrollHeight，IO mount/unmount 可见区间外消息；pinnedKeys 保活流式末条。
  - **→ 彻底移除的取舍（2026-06-18）**：①**IO/RO 时序致重叠（移除主因）**——IO 判可见 + RO 测高度异步回调与 Vue 响应式交织，卸载分支 height=0 时 minHeight fallback 仍有竞态窗口（reply1 移出 pinned + bubble 重建时 RO/IO 捕获 height=0 → slot 塌 0 → 后续上移重叠）；多次修 fallback（8abcd56）+ 禁用裁剪（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 文件名——debug（Dev 模式）用 `devflow-dev.db`，release（Build 模式）用 `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`（顶部 fixed，3s 自动消失）+ `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 对抗论证时被砍/降级，此后无表、无 IPC，query 方法全 `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-plugin（WASM/动态库）、df-stages（11 阶段节点 `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 拆分：路线A（store 留 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. **选路线 A（composable 挪逻辑、store 留 state）而非路线 B（Pinia 多 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 依赖；裸 B（df-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 正向拆分，方向相反、互不矛盾。
  - **否决项**：纯 A（per-module trait）= 全局 N 份发散契约，反模式；裸 B（df-ideas 直接依赖 df-ai）= 纯逻辑 crate 被 reqwest 污染、自检摩擦上升。
  - **退路**：若不愿加新 crate，trait 可放 `df-core`（语义稍糙——LLM 非领域类型，但零新 crate，可接受）。
- **状态**：📐 设计定稿（2026-06-14，4 项决策已定：① 拆分边界=仅 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 / CompletionRequest），df-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-06（execution_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 的状态推进链路。阶段1（7 态状态机 + 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-task（D-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 层保持 thin（task.rs 仅 3 行转发）守住 D3 不让业务逻辑下沉 IPC。前端对齐后端 7 态（D-260616-01，types.rs:131，激活 InReview/Testing/Blocked 三闸门态）。
- **状态**：✅ 阶段1 落地（commit d2cb38c：7态状态机 + 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.config，run 合并下沉。
- **原因/取舍**：原实现（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 agent：dag.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），延续此约定。硬编码模板随代码版本管理，零运行时配置开销。
- **状态**：📐 设计定稿待实施（②-6，batch33+）。

### 工作流回调语义：成功与任务推进解耦，失败按 target 退回（②-3/②-4/②-5）
- **决策**：工作流完成后回调 advance_task 的语义——**成功**（executor Ok）：task_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 自审闸门选**方案 A（AiNode 内部 return Err）**——自检失败时 return Err(SelfReviewFailed) 复用 DagExecutor 已有的 first_err 收敛 + ②-4 回调错误路径。不选方案 B（DAG edges 条件 + ConditionEngine，依赖暂缓的 T-260614-11）和方案 C（DagExecutor 核心循环改闸门钩子）。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 9–10 🚧 | P1 |
| 切对话不中断路由：部分场景运行时实测 | AI Chat 可靠性 | Sprint 8 🚧 | P1 |
| 技能联想「使用」：首批 3 类联想已做，联想后实际触发/执行技能未实现 | 技能/联想 | Sprint 8 | P2 |
| 灵感对抗评估接 LLM：论点/evidence 由 df-ai LlmProvider 生成（现启发式 fallback） | 灵感模块 | Sprint 9 📐 | P2 |
| 知识库 Tier 1：AI 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 每节点拿全新空 StateMachine：self.state_machine 从不传入 NodeContext，HumanNode is_cancelled 恒 false，取消机制失效 | 工作流引擎 | 2026-06-13 多代理探索 | P1 |
| 🟡 promote_idea 两步写非事务：INSERT project 成功后若 UPDATE idea 失败，项目存在但想法状态未变，补偿删除可修 | 灵感/立项 | 2026-06-13 代码审查 | P1 |
| 🔴 分离窗口（detached）跨窗口状态失效：用 localStorage 传递生成态快照（df-ai-gen/text），Tauri 多 webview 不共享 localStorage 致静默失效；用户点 X 关闭（非 closeDetachedWindow）后主窗口 `detached` 永真卡死（reattachPanel 死代码未接线）。需改 Tauri 全局 emit/listen 同步 + 窗口销毁事件复位 | AI Chat 分离窗口 | 2026-06-13 代码审查 | P1 |
| 模型能力声明与自动路由系统 Phase 1：ModelCapability 数据模型 + 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 3：Agent 内智能路由——Agentic Loop 每轮按子任务构造不同 TaskRequirements；成本预算控制；模型级联降级；跨 Provider 搜索 | 模型能力与路由 | 2026-06-13 📐 | P3 |
| 📋 已澄清「显示多开」= 多会话来回切可对话（非 AI Chat 窗口多开）[2026-06-17]：用户原意是「多个会话之间切换都可继续对话」（当前切换会话后旧会话生成态丢失）。关联 F-09 多会话架构决策——A 路线（单例 AiSession + 软隔离，Sprint 8 已落地切对话不中断路由）待实测验证是否满足需求（T-260614-02）；B 路线（真多会话，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-12）：devflow 语境「决策/决策需求点」= 日常开发功能细节取舍（为什么这么定），**非** aichat 决策能力升级（B 路线 coordinator/conditions）。后者属架构层，记 Phase2计划/模块文档，不混入本文档。
- **代码审查甄别原则**（2026-06-13）：审查发现问题时，按「运行时失败/数据损坏 → 简单清理 → 记录不动 → 不做」四档甄别。当前项目规模下，list_all 无 LIMIT、ALLOWED_COLUMNS 不分表、bool→int 重复等属「记录不动」——个人工具表不超千行，加分页/拆白名单是过度设计，维护成本 >> 收益。原则：**真实 bug 修、简单清理做、规模不到位的优化先不动**，保持全局简洁和扩展容易。

## 文档维护

### 文档历史项保留原则：标状态不删行 [2026-06-15]
- **决策**：所有文档（ARCHITECTURE.md + 模块文档）中的历史设计项**一律保留原文不删除**，仅在行末或旁注标注实现状态：`✅ 已实现` / `❌ 未实现（设计预留）` / `⚠️ 骨架空壳（有文件但无实质逻辑）` / `~~已删~~`（R-PD-X 等重构决策引用）。
- **原因/取舍**：全量核对报告(2026-06-15)发现 ARCHITECTURE.md 含多出虚构/过时项(ModelRouter 已删/Docker 等 5 节点未实现)。初版方案为「删虚构行+注」，用户两次明确否决删方案，要求保留全部历史项+标状态。理由：① 保留设计演进痕迹，接手方可理解"曾经考虑过什么、为什么没做"；② 删除会导致核对报告等交叉引用断链；③ 标状态列比删行信息量更大。
- **影响范围**：DOC-260615-01~14 全部文档修项均遵循此原则。已落地：ARCHITECTURE.md §5.4 ModelRouter 标 `❌ ~~已删~~` + §5.5 8 节点标 `✅` / `❌`（commit be38a44）。
- **状态**：✅ 2026-06-15 落地（用户两次否决删方案后定稿，首批标注已 commit）

**相关文档**：
- [Phase 1 架构决策](./Phase1架构决策-2026-06-12.md) — 架构级决策（ADR）
- [经验记录](./经验记录-2026-06-14.md) — 踩坑/约定/技巧/bug 排查教训
- [功能决策记录-归档](./功能决策记录-归档-2026-06-14.md) — 纯流水/老 Sprint/UX 微调/已被取代
- `PROGRESS.md` — 各 Sprint 工作流水与遗留
- [Phase 2 计划](../07-项目管理/Phase2计划-2026-06-12.md)
