diff --git a/crates/df-ai/src/anthropic_compat.rs b/crates/df-ai/src/anthropic_compat.rs index c088c8c..c4da0c1 100644 --- a/crates/df-ai/src/anthropic_compat.rs +++ b/crates/df-ai/src/anthropic_compat.rs @@ -80,7 +80,7 @@ impl AnthropicCompatProvider { /// - `api_key`: API 密钥(Anthropic 用 x-api-key 头,非 Bearer) /// - `default_model`: 默认模型名称 pub fn new(base_url: impl Into, api_key: impl Into, default_model: impl Into) -> Self { - // SW-260618-10: reqwest Client 构建抽 crate::build_provider_client(与 OpenAI 共用 DRY)。 + // reqwest Client 构建抽 crate::build_provider_client(与 OpenAI 共用 DRY)。 let client = crate::build_provider_client(); Self { client, @@ -890,7 +890,7 @@ mod tests { assert!(!c.finished); } - /// error 事件 → error=Some + finished=false(R-P1-1:避免残缺响应被当正常完成入库, + /// error 事件 → error=Some + finished=false避免残缺响应被当正常完成入库, /// 由 stream_llm 识别 error 非空发 AiError + 丢弃残缺,与 OpenAI 路径 Err 一致) #[test] fn anthropic_error_event_yields_error_not_finished() { diff --git a/crates/df-ai/src/anthropic_helpers.rs b/crates/df-ai/src/anthropic_helpers.rs index 86816c2..40251cb 100644 --- a/crates/df-ai/src/anthropic_helpers.rs +++ b/crates/df-ai/src/anthropic_helpers.rs @@ -61,12 +61,12 @@ pub(crate) struct AnthropicToolDef { /// Anthropic 同步响应 #[derive(Debug, Deserialize)] pub(crate) struct AnthropicResponse { - // SW-260618-25: Anthropic 响应反序列化字段,保留以对齐响应结构(响应 id),标注意图消除 dead_code warning + // Anthropic 响应反序列化字段,保留以对齐响应结构(响应 id),标注意图消除 dead_code warning #[allow(dead_code)] id: String, pub model: String, pub content: Vec, - // SW-260618-25: Anthropic 响应反序列化字段,保留以备调试/未来消费(如日志记录调用终止原因),标注意图消除 dead_code warning + // Anthropic 响应反序列化字段,保留以备调试/未来消费(如日志记录调用终止原因),标注意图消除 dead_code warning #[allow(dead_code)] stop_reason: Option, pub usage: AnthropicUsage, diff --git a/crates/df-ai/src/context/mod.rs b/crates/df-ai/src/context/mod.rs index 3cb65bd..86bc4bb 100644 --- a/crates/df-ai/src/context/mod.rs +++ b/crates/df-ai/src/context/mod.rs @@ -275,7 +275,7 @@ impl ContextManager { self.messages.iter().map(|t| t.message.clone()).collect() } - /// 只读最近 N 条消息(尾部切片 clone)。BE2(AC-EFF-R2-1):断路器/探索熔断检查等只关心 + /// 只读最近 N 条消息(尾部切片 clone)。BE2 断路器/探索熔断检查等只关心 /// 最近消息的读路径,用本方法避免 all_messages_clone 每轮全量 clone(长对话每轮 O(n) 深克隆浪费)。 /// 尾部顺序保持时间正序(与 all_messages_clone 一致,调用方从尾部反向扫即可)。 pub fn recent_messages(&self, n: usize) -> Vec { @@ -326,7 +326,7 @@ impl ContextManager { /// 弹出末尾连续的 assistant 消息(含其 tool_calls 三元组尾随 tool_result) /// - /// 用于「重新生成」(UX-02):删掉最后一条 AI 回复(可能跨多轮 tool_calls + tool_results + /// 用于「重新生成」删掉最后一条 AI 回复(可能跨多轮 tool_calls + tool_results /// 紧随其后),保留触发它的 user 消息,以便重跑 agentic loop 再生成。 /// /// 语义:从末尾向前弹出,直到弹出至少一条 assistant 消息;若弹出 assistant 后紧邻的更早 diff --git a/crates/df-ai/src/context/sanitize.rs b/crates/df-ai/src/context/sanitize.rs index 33bf338..a34eccc 100644 --- a/crates/df-ai/src/context/sanitize.rs +++ b/crates/df-ai/src/context/sanitize.rs @@ -34,7 +34,7 @@ use crate::provider::{ChatMessage, MessageRole, ToolCall}; pub fn sanitize_messages(messages: Vec) -> Vec { use std::collections::HashSet; - // step 0(UX-09):过滤 truncated 软删消息,不进 LLM 上下文。 + // step 0过滤 truncated 软删消息,不进 LLM 上下文。 // 编辑某条 user 消息后其后续消息标 truncated(保留 DB 可追溯),发送视图必须剔除, // 否则被编辑前的旧回复仍进入 LLM 历史,污染重生成语义。落库全量保留不受影响。 let messages: Vec = messages diff --git a/crates/df-ai/src/context_helpers.rs b/crates/df-ai/src/context_helpers.rs index 3473582..4fdd67a 100644 --- a/crates/df-ai/src/context_helpers.rs +++ b/crates/df-ai/src/context_helpers.rs @@ -1135,7 +1135,7 @@ mod tests { #[test] fn extract_key_info_single_huge_line_no_newline_compressed() { // 单行超大内容(50KB)原本逃逸压缩,现按字符数截断保留头尾。 - // 注:read_file 已豁免压缩(BUG-260801),此处用 run_command 验证通用压缩路径。 + // 注:read_file 已豁免压缩,此处用 run_command 验证通用压缩路径。 let content = "x".repeat(50_000); let result = extract_key_info(&content, "run_command"); assert!(result.len() < content.len(), "单行超长应压缩: {} >= {}", result.len(), content.len()); @@ -1149,7 +1149,7 @@ mod tests { fn extract_key_info_json_huge_string_field_truncated() { // JSON 对象中大字符串字段(单行少行)逃逸压缩。 // 如 `{"path":"src/main.rs","content":"单行超大文本..."}`。 - // 注:read_file 已豁免压缩(BUG-260801),此处用 run_command 验证通用压缩路径。 + // 注:read_file 已豁免压缩,此处用 run_command 验证通用压缩路径。 let large = "z".repeat(10_000); let content = format!("{{\"path\":\"src/main.rs\",\"content\":\"{}\"}}", large); let result = extract_key_info(&content, "run_command"); @@ -1171,7 +1171,7 @@ mod tests { #[test] fn extract_key_info_eleven_lines_triggers_compression() { // 边界:行数 == 11(刚超 kept_boundary=10)→ 触发压缩,含标记 - // 注:read_file 已豁免压缩(BUG-260801),此处用 run_command 验证通用压缩路径。 + // 注:read_file 已豁免压缩,此处用 run_command 验证通用压缩路径。 let lines: Vec = (1..=11).map(|i| format!("line {}", i)).collect(); let content = lines.join("\n"); let result = extract_key_info(&content, "run_command"); @@ -1185,7 +1185,7 @@ mod tests { // 边界:错误行恰在头部区间内(idx < head_end)→ 不重复插入(头部已含) // 错误行在尾部区间内(idx >= tail_start)→ 不重复插入(尾部已含) // 错误行在中间区间 → 标注 [行 N] 插入 - // 注:read_file 已豁免压缩(BUG-260801),此处用 run_command 验证通用压缩路径。 + // 注:read_file 已豁免压缩,此处用 run_command 验证通用压缩路径。 let mut lines: Vec = (1..=20).map(|i| format!("norm {}", i)).collect(); // idx=2(头部区间 [0,5))错误行 → 头部已含,不在 error_lines(扫描跳过 head/tail) lines[2] = "error in head zone".to_string(); @@ -1347,7 +1347,7 @@ mod tests { assert_eq!(extract_pending_tc_id(""), None); } - // ── read_file 豁免压缩(BUG-260801: AI 定向读代码不应被折叠) ── + // ── read_file 豁免压缩AI 定向读代码不应被折叠) ── // // 根因:extract_key_info 的 JSON 分支对 content 字段(文件内容)行数 > 10 即折叠中间为 // "(压缩中间内容)"。read_file limit=100 读 28KB(100 行)→ 触发 → AI 只拿到首尾各 5 行, diff --git a/crates/df-ai/src/lib.rs b/crates/df-ai/src/lib.rs index 42b5165..67d52d5 100644 --- a/crates/df-ai/src/lib.rs +++ b/crates/df-ai/src/lib.rs @@ -56,7 +56,7 @@ use reqwest::Client; // df-ai-core 直接暴露,供需要直接引用 trait crate 的下游(可选)。 pub use df_ai_core; -/// SW-260618-10: 构建 Provider 共用的 reqwest Client(OpenAI/Anthropic Provider::new DRY)。 +/// 构建 Provider 共用的 reqwest Client(OpenAI/Anthropic Provider::new DRY)。 /// /// connect_timeout 30s 防连接阶段静默 hang(网络静默断);不设总 timeout——reqwest 的 /// `.timeout()` 会限制整个响应 body 时长,流式长生成任务会被误砍。读取阶段中途静默由上层 diff --git a/crates/df-ai/src/model_fetch_helpers.rs b/crates/df-ai/src/model_fetch_helpers.rs index 2051a9f..1e2972d 100644 --- a/crates/df-ai/src/model_fetch_helpers.rs +++ b/crates/df-ai/src/model_fetch_helpers.rs @@ -239,7 +239,7 @@ mod tests { // SenseNova U1 系列是图像生成模型(走 /v1/images/generations,非 chat completions), // /v1/models 会返回但不应留为 chat 模型,否则用户选它对话会失败。 assert!(is_non_chat_model("sensenova-u1-fast")); - assert!(is_non_chat_model("SenseNova-U1-Fast")); // 大小写无关 + assert!(is_non_chat_model("SenseNova-ast")); // 大小写无关 assert!(is_non_chat_model("sensenova-u1-pro")); // 通用图像生成命名也剔 assert!(is_non_chat_model("infographic-v1")); diff --git a/crates/df-ai/src/openai_compat.rs b/crates/df-ai/src/openai_compat.rs index e95c17c..63e74e2 100644 --- a/crates/df-ai/src/openai_compat.rs +++ b/crates/df-ai/src/openai_compat.rs @@ -47,7 +47,7 @@ impl OpenAICompatProvider { /// - `api_key`: API 密钥 /// - `default_model`: 默认模型名称 pub fn new(base_url: impl Into, api_key: impl Into, default_model: impl Into) -> Self { - // SW-260618-10: reqwest Client 构建抽 crate::build_provider_client(与 Anthropic 共用 DRY)。 + // reqwest Client 构建抽 crate::build_provider_client(与 Anthropic 共用 DRY)。 // connect_timeout/回退策略集中此处,未来改一处即可(见 lib.rs::build_provider_client)。 let client = crate::build_provider_client(); Self { diff --git a/crates/df-ai/src/openai_helpers.rs b/crates/df-ai/src/openai_helpers.rs index f1083f0..da469ae 100644 --- a/crates/df-ai/src/openai_helpers.rs +++ b/crates/df-ai/src/openai_helpers.rs @@ -67,7 +67,7 @@ pub(crate) struct OpenAiResponse { #[derive(Debug, Deserialize)] pub(crate) struct OpenAiChoice { pub message: OpenAiMessageResp, - // SW-260618-24: OpenAI 响应反序列化字段,保留以备调试/未来消费(如日志记录调用终止原因),标注意图消除 dead_code warning + // OpenAI 响应反序列化字段,保留以备调试/未来消费(如日志记录调用终止原因),标注意图消除 dead_code warning #[allow(dead_code)] pub finish_reason: Option, } @@ -84,7 +84,7 @@ pub(crate) struct OpenAiMessageResp { #[derive(Debug, Deserialize)] pub(crate) struct OpenAiToolCallResp { pub id: String, - // SW-260618-24: 反序列化 #[serde(rename="type")] 字段,保留以对齐 OpenAI 响应结构,标注意图消除 dead_code warning + // 反序列化 #[serde(rename="type")] 字段,保留以对齐 OpenAI 响应结构,标注意图消除 dead_code warning #[allow(dead_code)] #[serde(rename = "type")] pub call_type: String, diff --git a/crates/df-ai/src/retry.rs b/crates/df-ai/src/retry.rs index bb8ddd7..c502359 100644 --- a/crates/df-ai/src/retry.rs +++ b/crates/df-ai/src/retry.rs @@ -40,7 +40,7 @@ const JITTER_RATIO: f64 = 1.0; /// 一次尝试的分类结果 —— 在 anyhow 不透明化前决定是否值得重试。 /// -/// CR-30-1: 暴露给 agentic.rs 流前重试复用(Init 失败分类 Fatal/Retryable 决定是否重试)。 +/// 暴露给 agentic.rs 流前重试复用(Init 失败分类 Fatal/Retryable 决定是否重试)。 #[derive(Debug)] pub enum AttemptOutcome { /// 成功,携带结果。 @@ -60,7 +60,7 @@ pub fn is_reqwest_error_retryable(e: &reqwest::Error) -> bool { /// 判定 HTTP 状态码是否可重试: 5xx 与 429 重试,其余(含 4xx)不重试。 /// -/// CR-30-1: 暴露给 agentic.rs 流前重试复用(4xx Fatal 立即放弃,5xx/429 重试)。 +/// 暴露给 agentic.rs 流前重试复用(4xx Fatal 立即放弃,5xx/429 重试)。 pub fn is_status_retryable(status: u16) -> bool { status == 429 || (500..600).contains(&status) } @@ -91,7 +91,7 @@ pub fn backoff_delay(attempt: u32) -> Duration { /// 调用方约定: `attempt_fn` 内部应保留完整请求重发能力(每次重建 RequestBuilder), /// 因为 reqwest::RequestBuilder 一次 `.send()` 后不可复用。 /// -/// CR-30-1: 仍 pub(crate)(complete() 内部用),流前重试 stream_recv/agentic 不走此函数 +/// 仍 pub(crate)(complete() 内部用),流前重试 stream_recv/agentic 不走此函数 /// (流式 request 不可整体 retry_with_backoff 包裹,需在 agentic loop 内手写循环复用 /// backoff_delay + is_status_retryable)。 pub(crate) async fn retry_with_backoff( diff --git a/crates/df-execute/src/shell.rs b/crates/df-execute/src/shell.rs index 6a02fcf..5196131 100644 --- a/crates/df-execute/src/shell.rs +++ b/crates/df-execute/src/shell.rs @@ -258,7 +258,7 @@ fn build_command(request: ShellRequest) -> tokio::process::Command { } }; - // CR-15-1: kill_on_drop(true) 让 Command 被 drop 时主动 kill 子进程。 + // kill_on_drop(true) 让 Command 被 drop 时主动 kill 子进程。 // 配合 tokio::time::timeout 超时场景:超时 drop future → Command 析构 → kill 子进程, // 不再让超时后的命令变孤儿继续后台跑(长 hang 命令/死循环仍占资源)。 // 对齐 tool_registry.rs:514「进程已终止」文案名副其实。tokio 1.52.3 支持。 diff --git a/crates/df-ideas/src/adversarial_helpers.rs b/crates/df-ideas/src/adversarial_helpers.rs index 0580882..49ee83f 100644 --- a/crates/df-ideas/src/adversarial_helpers.rs +++ b/crates/df-ideas/src/adversarial_helpers.rs @@ -296,7 +296,7 @@ pub(crate) fn extract_json(text: &str) -> String { // (原实现在此处 return body.trim() 会把语言标记/尾随文字一起喂 serde, // 必然失败降级启发式,即便正则本可救回。) } - // 兜底:围栏不在开头或混杂前后文字 → 正则提取首个 JSON 对象(CR-40-2)。 + // 兜底:围栏不在开头或混杂前后文字 → 正则提取首个 JSON 对象。 // (?s) 让 . 匹配换行,贪婪 {*} 取首 { 到末 },覆盖嵌套对象。 // 提取失败(无 { })则返回 trimmed 走原文 serde 报错降级,语义不变。 static JSON_RE: std::sync::OnceLock = std::sync::OnceLock::new(); diff --git a/crates/df-ideas/src/promotion.rs b/crates/df-ideas/src/promotion.rs index 1bbcd16..f8c6cb6 100644 --- a/crates/df-ideas/src/promotion.rs +++ b/crates/df-ideas/src/promotion.rs @@ -1,6 +1,5 @@ //! 想法晋升 — PromotionResult(promote_idea IPC 返回类型)。 //! 实际晋升路径:commands/idea.rs::promote_idea → df-project::ProjectManager::create_from_idea。 -//! 历史空壳 IdeaPromoter/PromotionPolicy 已删(R-PD-14)。 use serde::Serialize; diff --git a/crates/df-mcp/src/server.rs b/crates/df-mcp/src/server.rs index 4c6d9e3..5ce6d2c 100644 --- a/crates/df-mcp/src/server.rs +++ b/crates/df-mcp/src/server.rs @@ -152,7 +152,7 @@ where let resp = dispatch(ctx, config.read_only, req.id.clone(), method).await; write_response(&mut writer, &resp).await?; if let Some(name) = tool_name { - // MC-5(MCP-6):仅业务成功才触发写回调——防业务失败(如 update_project 非法 status、 + // MC-5仅业务成功才触发写回调——防业务失败(如 update_project 非法 status、 // create_project 空名等 result.isError=true)仍假触发 df-data-changed。 if is_success_tool_call(&resp) { fire_write_hook(config, &name); @@ -169,7 +169,7 @@ where /// - `result` 内 `isError=true`(MCP 业务错,handler 返 `CallToolResult::error`)→ 失败 /// - 其余(正常 result / initialize/ping 等非 tools/call)→ 成功 /// -/// MC-5(MCP-6):stdio(main_loop)与 HTTP(server_http on_tool_call)共用此判定, +/// stdio(main_loop)与 HTTP(server_http on_tool_call)共用此判定, /// 保证两 transport 对「业务失败不触发写回调」语义一致。 pub(crate) fn is_success_tool_call(resp: &Response) -> bool { if resp.error.is_some() { @@ -651,7 +651,7 @@ mod tests { #[tokio::test] async fn main_loop_no_write_callback_on_business_failure() { - // MC-5(MCP-6):业务失败(handler 返 isError=true)不得触发写回调(防假 df-data-changed)。 + // MC-5业务失败(handler 返 isError=true)不得触发写回调(防假 df-data-changed)。 // create_project 空名(name 为纯空白)→ MC-6 拒空 → isError=true。 let ctx = test_ctx().await; let calls: Arc>> = Arc::new(Mutex::new(Vec::new())); diff --git a/crates/df-mcp/src/server_http.rs b/crates/df-mcp/src/server_http.rs index f18330b..47b1a60 100644 --- a/crates/df-mcp/src/server_http.rs +++ b/crates/df-mcp/src/server_http.rs @@ -163,7 +163,7 @@ async fn handle_body(state: &McpHttpState, body: &[u8]) -> Outcome { let resp = dispatch(&state.ctx, state.read_only, req.id.clone(), method).await; // ⑥ 成功 tools/call(JSON-RPC 无 error 且 result.isError≠true)→ on_tool_call 回调 - // (桌面端据此刷新 GUI)。MC-5/MCP-6:业务失败(handler 返 isError=true)不触发, + // (桌面端据此刷新 GUI)。MC-5/业务失败(handler 返 isError=true)不触发, // 防假 df-data-changed;与 stdio fire_write_hook 共用 is_success_tool_call,两 transport 统一。 if crate::server::is_success_tool_call(&resp) { if let (Some(name), Some(cb)) = (tool_name, &state.on_tool_call) { diff --git a/crates/df-mcp/src/tools.rs b/crates/df-mcp/src/tools.rs index 48d8ce4..f517eff 100644 --- a/crates/df-mcp/src/tools.rs +++ b/crates/df-mcp/src/tools.rs @@ -247,7 +247,7 @@ fn check_expected_updated_at(args: &Value, db_updated_at: &str) -> Result<(), Ca } // ============================================================ -// 跨实体校验(防 B-260801-01:跨实体误操作) +// 跨实体校验(防 跨实体误操作) // ============================================================ // // 各实体表(projects / tasks / ideas)独立存储,Repo::get_by_id 只查本表。 @@ -352,7 +352,7 @@ fn create_project(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> Ok(v) => v, Err(r) => return Box::pin(std::future::ready(r)), }; - // MC-6(MCP-4):name trim + 拒空(对齐 GUI create_with_binding / ProjectManager::create, + // name trim + 拒空(对齐 GUI create_with_binding / ProjectManager::create, // 防空白项目名进库——required 只保证传参,不保证非空白)。 let name = name.trim().to_string(); if name.is_empty() { @@ -406,7 +406,7 @@ fn update_project(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> let existing = match repo.get_by_id(&id).await { Ok(Some(p)) => p, Ok(None) => { - // 跨实体校验(B-260801-01):id 可能属于 task/idea,给精确错误防误操作 + // 跨实体校验id 可能属于 task/idea,给精确错误防误操作 if let Some(msg) = cross_entity_err(&db, &id, "project").await { return CallToolResult::error(msg); } @@ -421,7 +421,7 @@ fn update_project(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> // 部分更新:name/description 缺省回退 existing,避免空默认清空数据 let name = arg_str(&args, "name").unwrap_or_else(|_| existing.name.clone()); let description = arg_str(&args, "description").unwrap_or_else(|_| existing.description.clone()); - // MC-1(MCP-1):status 不再写入——移除 status 参数对 DB 的影响,保留原值(对齐 update_task + // status 不再写入——移除 status 参数对 DB 的影响,保留原值(对齐 update_task // 收口思路:status 是生命周期状态,不经 update_project 旁路改写,防制造 GUI 不可能的非法跳态)。 // 客户端传 status 参数被静默忽略(保留原值);项目状态变更走专用流转路径。 let status = existing.status; @@ -517,7 +517,7 @@ fn bind_directory(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> Ok(false) => return CallToolResult::error(format!("项目不存在: {id}")), Err(e) => return err_str(e), } - // MC-6(MCP-2):两步写降级——stack 写失败不中断绑定(path 已落库,核心目标达成)。 + // MC-6两步写降级——stack 写失败不中断绑定(path 已落库,核心目标达成)。 // tracing::warn 记录,仍返回已绑定结果(path 已更新);GUI 侧可后续经 relocate 重探测。 if let Err(e) = repo.update_field(&id, "stack", &stack_json).await { tracing::warn!( @@ -578,7 +578,7 @@ fn create_task(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> { Ok(v) => v, Err(r) => return Box::pin(std::future::ready(r)), }; - // MC-6(MCP-4):title trim + 拒空(对齐 GUI create_task BE-CMD-1,防空白标题进库)。 + // title trim + 拒空(对齐 GUI create_task BE-CMD-1,防空白标题进库)。 let title = title.trim().to_string(); if title.is_empty() { return Box::pin(std::future::ready(CallToolResult::error( @@ -669,7 +669,7 @@ fn update_task(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> { let existing = match repo.get_by_id(&id).await { Ok(Some(t)) => t, Ok(None) => { - // 跨实体校验(B-260801-01):id 可能属于 project/idea,给精确错误防误操作 + // 跨实体校验id 可能属于 project/idea,给精确错误防误操作 if let Some(msg) = cross_entity_err(&db, &id, "task").await { return CallToolResult::error(msg); } @@ -795,7 +795,7 @@ fn create_idea(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> { Err(r) => return Box::pin(std::future::ready(r)), }; let description = arg_str_or(&args, "description", ""); - // MC-6(MCP-3):priority 值域校验(复用 df-storage normalize_priority 与 GUI/AI 工具同源, + // priority 值域校验(复用 df-storage normalize_priority 与 GUI/AI 工具同源, // 拦截 99 等越界值——原 arg_int_or 直落 99 被静默吞为前端 Critical,IPC 拒/MCP 放行漂移)。 let priority = match df_storage::crud::normalize_priority( args.get("priority").and_then(|v| v.as_i64()), @@ -845,7 +845,7 @@ fn update_idea(ctx: &Ctx, args: Value) -> BoxFuture<'static, CallToolResult> { let existing = match repo.get_by_id(&id).await { Ok(Some(i)) => i, Ok(None) => { - // 跨实体校验(B-260801-01):id 可能属于 project/task,给精确错误防误操作 + // 跨实体校验id 可能属于 project/task,给精确错误防误操作 if let Some(msg) = cross_entity_err(&db, &id, "idea").await { return CallToolResult::error(msg); } @@ -1451,7 +1451,7 @@ mod tests { } } - // ── 跨实体校验(B-260801-01):update_* 检测 id 属于其他实体时报精确错误 ── + // ── 跨实体校验update_* 检测 id 属于其他实体时报精确错误 ── // // 各实体表独立,Repo::get_by_id 只查本表。当 id 实属另一实体时, // 旧实现只报「任务/项目/想法不存在」(误导),改后报「id 属于 X,不能用 update_Y 修改」。 diff --git a/crates/df-nodes/src/ai_node.rs b/crates/df-nodes/src/ai_node.rs index 0401d31..bea523a 100644 --- a/crates/df-nodes/src/ai_node.rs +++ b/crates/df-nodes/src/ai_node.rs @@ -124,7 +124,7 @@ impl Node for AiNode { "base_url": { "type": "string", "description": "(已废弃过渡)明文 API 地址,改用 provider_id" }, "api_key": { "type": "string", "description": "(已废弃过渡)明文 API 密钥,改用 provider_id;密钥经 secret 解析" } }, - // SW-260802-01: schema 与 handler 行为对齐 — config 层 required=[] 正确,但 prompt 运行时必填。 + // schema 与 handler 行为对齐 — config 层 required=[] 正确,但 prompt 运行时必填。 // prompt: execute → parse_params(ai_node_helpers.rs:189-200) 取 inputs["prompt"] > config.prompt, // 两者皆无则 Err("缺少必填参数: prompt")。即 prompt 真必填,但可由上游节点注入, // JSON Schema 只校验 config 属性无法表达"二选一",故 required 留空 + description 标注兜底来源, @@ -427,7 +427,7 @@ mod tests { // ai_self_review_node.rs(与被测代码同位,纯搬运)。 // ============================================================ - // SW-260802-01: schema required 与 handler 行为对齐测试 + // schema required 与 handler 行为对齐测试 // ============================================================ // // 真实 bug(误判修正):原注释称"prompt/provider_id 均留空走兜底",实则 prompt 运行时必填 —— diff --git a/crates/df-nodes/src/ai_node_helpers.rs b/crates/df-nodes/src/ai_node_helpers.rs index 8432c2c..c184043 100644 --- a/crates/df-nodes/src/ai_node_helpers.rs +++ b/crates/df-nodes/src/ai_node_helpers.rs @@ -64,7 +64,7 @@ pub(crate) struct ResolvedProvider { /// 无任何 provider → 友好错误「未配置 AI Provider」。 /// /// model:config["model"] 非空用之,否则 record.default_model,再否则 "gpt-4o-mini" 占位。 -/// SW-260618-09: provider 三件套 DRY —— resolve_provider + parse_params 合并 +/// provider 三件套 DRY —— resolve_provider + parse_params 合并 /// (AiNode/AiSelfReviewNode execute 逐字重复)。 pub(crate) async fn resolve_and_parse( db: &Arc, @@ -72,7 +72,7 @@ pub(crate) async fn resolve_and_parse( inputs: &HashMap, ) -> anyhow::Result { let provider_cfg = resolve_provider(db, config).await?; - // P0-B: parse_params 缺 prompt 报错时兜底 —— 工作流推进链模板 AiNode config 无 prompt + // parse_params 缺 prompt 报错时兜底 —— 工作流推进链模板 AiNode config 无 prompt // (in_progress/testing/done 三链首节点 AiNode 仅带 provider_id, prompt 留空), // 原行为直接 Err 致整链必失败。兜底:从 config.task_id 读 TaskRecord 生成基于 // title/description 的执行 prompt;无 task_id 用通用默认。不改 LLM 调用结构, @@ -134,7 +134,7 @@ fn default_fallback_prompt() -> String { "请根据上下文完成当前任务,产出可执行结果并自检。若需更多信息请说明。".to_string() } -/// SW-260618-09: build_provider 5 行封装 DRY(AiNode/AiSelfReviewNode execute 逐字重复)。 +/// build_provider 5 行封装 DRY(AiNode/AiSelfReviewNode execute 逐字重复)。 pub(crate) fn provider_from_params(p: &AiNodeParams) -> Box { df_ai::build_provider( &p.provider.protocol, diff --git a/crates/df-nodes/src/ai_self_review_node.rs b/crates/df-nodes/src/ai_self_review_node.rs index 518e958..2710aa5 100644 --- a/crates/df-nodes/src/ai_self_review_node.rs +++ b/crates/df-nodes/src/ai_self_review_node.rs @@ -225,7 +225,7 @@ impl Node for AiSelfReviewNode { // // 设计选型(三方案对比,见 commit/设计文档): // A) AiNode 内部门控(本方案):1 处 return Err,复用 executor first_err + ②-4 回调, - // 零 executor 核心改动,零依赖暂缓的条件引擎(T-260614-11)。最简。 + // 零 executor 核心改动,零依赖暂缓的条件引擎。最简。 // B) DAG edges 条件 + ConditionEngine 求值:依赖 T-260614-11(暂缓),executor 当前 // 完全不评估 edge.condition(topological_layers 无条件收录所有边),须先做条件 // 引擎 Phase1+2 才能用 → 拆波。 @@ -264,7 +264,7 @@ impl Node for AiSelfReviewNode { "max_tokens": { "type": "integer" }, "gate": { "type": "boolean", "description": "闸门开关:false(默认)=自审辅助,verdict 仅透传展示;true=自审结果作 DAG 闸门,verdict=fail 返回 Err 阻断下游(工作流 failed → ②-4 退回),verdict=unknown/pass 放行" } }, - // SW-260802-01: schema 与 handler 行为对齐 — required 仅列 handler 真正强制必填的字段。 + // schema 与 handler 行为对齐 — required 仅列 handler 真正强制必填的字段。 // task_id: execute 第 100-104 行缺 task_id 直接 Err("缺少必填参数: task_id"),真必填 → 保留。 // provider_id: execute 调 resolve_and_parse → resolve_provider(ai_node_helpers.rs:100-108), // 空串走路径 2(老明文)/路径 3(默认 provider)兜底,运行时非必填 → 移出 required。 @@ -703,7 +703,7 @@ mod tests { } // ============================================================ - // SW-260802-01: schema required 与 handler 行为对齐测试 + // schema required 与 handler 行为对齐测试 // ============================================================ // // 真实 bug:schema `required=["task_id","provider_id"]` 与 handler 不一致 —— diff --git a/crates/df-nodes/src/human_node.rs b/crates/df-nodes/src/human_node.rs index 14d908d..0102904 100644 --- a/crates/df-nodes/src/human_node.rs +++ b/crates/df-nodes/src/human_node.rs @@ -56,7 +56,7 @@ impl Node for HumanNode { let mut rx = ctx.event_bus.subscribe(); // 2. 发送人工审批请求到事件总线 - // send 是 async fn(broadcast 同步发但 async 包装),必须 await 否则 Future 不 poll、Request 不进 channel(B-03b-R6) + // send 是 async fn(broadcast 同步发但 async 包装),必须 await 否则 Future 不 poll、Request 不进 channel(b-R6) let _ = ctx.event_bus .send(WorkflowEvent::HumanApprovalRequest { execution_id: ctx.execution_id.clone(), @@ -292,7 +292,7 @@ mod tests { #[tokio::test] async fn request_is_emitted_to_bus() { - // B-03b-R8: 验证 HumanNode 真发出 HumanApprovalRequest 到事件总线 + // b-R8: 验证 HumanNode 真发出 HumanApprovalRequest 到事件总线 // (R6 修复前 execute 内 event_bus.send().await 缺 await → Request 未进 channel,此测会超时失败) let bus = EventBus::new(); let mut rx = bus.subscribe(); // subscribe 先于 execute send(broadcast 不回放) @@ -375,7 +375,7 @@ mod tests { #[tokio::test] async fn invalid_decision_ignored_then_timeout() { - // R-P1-6: options 非空且 decision ∉ options → 不立即 Err(一次手误不应杀死节点), + // options 非空且 decision ∉ options → 不立即 Err(一次手误不应杀死节点), // warn 记录 + continue 续等下一条合法 Response;此处仅发一条非法的,短超时验证 → Err 超时。 let bus = EventBus::new(); let ctx = make_ctx( @@ -421,7 +421,7 @@ mod tests { #[tokio::test] async fn empty_decision_ignored_then_timeout_even_with_empty_options() { - // R-P1-6: 空 decision 始终非法,但同样不立即 Err——warn + continue 续等,短超时验证 → Err 超时。 + // 空 decision 始终非法,但同样不立即 Err——warn + continue 续等,短超时验证 → Err 超时。 let bus = EventBus::new(); let ctx = make_ctx(&bus, "exec-1", "node-h", json!({ "options": [], "timeout_secs": 1 })); let bus_clone = bus.clone(); @@ -441,7 +441,7 @@ mod tests { // 在 execute 运行期间通道无法 Closed(至少该 sender 存活)。 // 该分支仅在所有 sender drop 后可达,需独立集成测试,此处不强造。 - // ===== B-03b-R8: executor 级端到端集成测试 ===== + // ===== b-R8: executor 级端到端集成测试 ===== // 上述单测均为 HumanNode.execute 直接调用级,不覆盖 executor 驱动含 human 的 DAG。 // 端到端覆盖 NodeStarted/Completed 事件流转、共享 state_machine、Request 经 executor 驱动真发出、 // outputs 收集,是 R6(send 缺 await)/R7(契约失配)的存活土壤。 @@ -470,7 +470,7 @@ mod tests { } } - /// B-03b-R8: executor 驱动 a(SleepNode) → b(HumanNode) 两层 DAG 端到端。 + /// b-R8: executor 驱动 a(SleepNode) → b(HumanNode) 两层 DAG 端到端。 /// a 完成 → b 阻塞等审批 → 外部发 Response(模拟 approve_human_approval IPC) → b Ok → 工作流完成。 /// 验证:Request 经 executor 驱动真发出(R6)、outputs 收集两层、共享 state_machine 两节点皆 Completed。 #[tokio::test] @@ -532,7 +532,7 @@ mod tests { assert_eq!(sm.get(&"b".to_string()), NodeStatus::Completed); } - /// B-03b-R8: executor 驱动 human DAG,外部 set_cancelled(模拟 cancel_workflow_node IPC) + /// b-R8: executor 驱动 human DAG,外部 set_cancelled(模拟 cancel_workflow_node IPC) /// → human cancel_tick 命中 is_cancelled → Err → executor 跳过 set_failed(Cancelled 已终态,R1) /// → run 返回取消 Err + 状态保持 Cancelled。覆盖取消在真实 HumanNode 上的端到端(R2)。 #[tokio::test] diff --git a/crates/df-nodes/src/task_advance_node.rs b/crates/df-nodes/src/task_advance_node.rs index 059f8fb..9dc7159 100644 --- a/crates/df-nodes/src/task_advance_node.rs +++ b/crates/df-nodes/src/task_advance_node.rs @@ -35,7 +35,7 @@ use crate::task_state_machine::{can_transition, is_regression, is_valid_state, A /// 3. 状态机(三类拒绝,错误区分供前端分辨): /// - 同态拒绝(from==to):Validation「相同状态,无需推进」(非状态机违例,是空操作) /// - 非法转换(跳态/终态后继等):InvalidState「非法状态转换 X→Y」(含 from/to 上下文) -/// - 两类拒绝均附 legal_targets(from) 合法目标列表,供 LLM 下次选对目标态(AC-5) +/// - 两类拒绝均附 legal_targets(from) 合法目标列表,供 LLM 下次选对目标态 /// - can_transition 闸门矩阵判否即此分支 /// 4. 原子写:advance_status_atomic CAS,to 是退回转换时 bump_rounds=true /// 5. CAS 失败(affected==0):状态已被并发改动 → InvalidState 错误(防 TOCTOU 静默成功) @@ -468,7 +468,7 @@ mod tests { #[tokio::test] async fn illegal_skip_rejected() { - // CR-01-D: 非法转换(跳态)归 InvalidState,且消息含 from→to 上下文与「非法状态转换」。 + // 非法转换(跳态)归 InvalidState,且消息含 from→to 上下文与「非法状态转换」。 let repo = setup().await; repo.insert(rec("t1", TaskStatus::Todo)).await.unwrap(); let err = advance_task_atomic(&repo, "t1", "done").await.unwrap_err(); @@ -484,7 +484,7 @@ mod tests { #[tokio::test] async fn terminal_done_no_successor() { - // CR-01-D: 终态无后继也是非法转换路径,归 InvalidState(同 illegal_skip_rejected)。 + // 终态无后继也是非法转换路径,归 InvalidState(同 illegal_skip_rejected)。 let repo = setup().await; repo.insert(rec("t1", TaskStatus::Done)).await.unwrap(); let err = advance_task_atomic(&repo, "t1", "todo").await.unwrap_err(); @@ -498,7 +498,7 @@ mod tests { #[tokio::test] async fn same_status_rejected() { - // CR-01-D: 同态拒绝(from==to)与非法转换(跳态)错误类型区分。 + // 同态拒绝(from==to)与非法转换(跳态)错误类型区分。 // 同态属空操作,归 Validation「相同状态,无需推进」; // 非法转换归 InvalidState「非法状态转换 X→Y」(见 illegal_skip_rejected)。 let repo = setup().await; diff --git a/crates/df-relay/src/main.rs b/crates/df-relay/src/main.rs index 0d903ec..0651d2f 100644 --- a/crates/df-relay/src/main.rs +++ b/crates/df-relay/src/main.rs @@ -13,7 +13,7 @@ use df_relay::{DefaultRelayServer, RelayError, RelayServer}; #[tokio::main] async fn main() -> df_relay::Result<()> { tracing_subscriber::fmt::init(); - // RLY-5:token 未设置 → 启动即返回错误(exit code 1 + 明确消息),而非运行期 panic。 + // token 未设置 → 启动即返回错误(exit code 1 + 明确消息),而非运行期 panic。 // relay.rs expected_token() 已改为返回 Option 兜底,但 fail-fast 比慢速拒连更早暴露配置错误。 if std::env::var("DF_RELAY_TOKEN").is_err() { return Err(RelayError::Start( diff --git a/crates/df-relay/src/relay.rs b/crates/df-relay/src/relay.rs index a7b44c1..dcdef98 100644 --- a/crates/df-relay/src/relay.rs +++ b/crates/df-relay/src/relay.rs @@ -15,7 +15,7 @@ //! `device_id` 并冒充该设备收发指令**。生产级 per-device token(配对时按 device 颁发独立 token //! 并存储)涉及配对流程改造(需 df-miniapp/df-tunnel 配合),当前批次不实施,仅做最小加固: //! - 连接建立时校验 `device_id` 格式(非空 + 长度上限,见 `validate_device_id`)。 -//! - token 比较用常量时间比较(RLY-4),防时序侧信道。 +//! - token 比较用常量时间比较,防时序侧信道。 //! 完整 per-device 鉴权留设计文档 Phase3。 //! //! AiChatEvent JSON 透传:relay 不解析 payload,只按 device_id + 方向转发。 @@ -41,13 +41,13 @@ use crate::conn::{next_conn_id, ConnHandle, ConnId, RelayState}; use crate::error::{RelayError, Result}; /// 读取期望 token(必需:env `DF_RELAY_TOKEN` 必须设置)。 -/// RLY-5:不再 panic,改为返回 Option——缺失时由连接层显式拒绝握手(main 启动时也已校验)。 +/// 不再 panic,改为返回 Option——缺失时由连接层显式拒绝握手(main 启动时也已校验)。 /// 生产级鉴权(每 device 独立 token + 过期刷新)留 Phase3。 fn expected_token() -> Option { std::env::var("DF_RELAY_TOKEN").ok() } -/// RLY-3:入站文本帧大小上限(1 MiB)。 +/// 入站文本帧大小上限(1 MiB)。 /// 防恶意/异常客户端发超大帧耗尽内存与转发带宽;超限直接断开连接。 const MAX_FRAME_BYTES: usize = 1 * 1024 * 1024; @@ -272,7 +272,7 @@ async fn handle_connection(socket: WebSocket, state: RelayState, expected: Clien let _ = socket_tx.close().await; return; } - // RLY-5:token 未配置(服务启动时应已由 main 校验)→ 拒绝握手而非 panic + // token 未配置(服务启动时应已由 main 校验)→ 拒绝握手而非 panic let Some(expected) = expected_token() else { tracing::error!("DF_RELAY_TOKEN 未设置,拒绝握手"); let _ = send_text( @@ -283,7 +283,7 @@ async fn handle_connection(socket: WebSocket, state: RelayState, expected: Clien let _ = socket_tx.close().await; return; }; - // RLY-4:常量时间比较防时序侧信道 + // 常量时间比较防时序侧信道 if !constant_time_eq(&hello.token, &expected) { tracing::warn!( device_id = %hello.device_id, @@ -332,7 +332,7 @@ async fn handle_connection(socket: WebSocket, state: RelayState, expected: Clien maybe_msg = socket_rx.next() => { match maybe_msg { Some(Ok(Message::Text(text))) => { - // RLY-3:入站帧超限(>1MiB)直接断开,防大帧耗尽内存/带宽 + // 入站帧超限(>1MiB)直接断开,防大帧耗尽内存/带宽 if text.len() > MAX_FRAME_BYTES { tracing::warn!( conn_id = conn_id.0, @@ -402,7 +402,7 @@ async fn recv_hello(rx: &mut futures_util::stream::SplitStream) -> Re } _ => return Err(RelayError::Client("握手首帧类型非法".into())), }; - // RLY-3:Hello 帧同样限长(Hello 结构很小,超限视为异常/恶意) + // Hello 帧同样限长(Hello 结构很小,超限视为异常/恶意) if text.len() > MAX_FRAME_BYTES { return Err(RelayError::Client(format!( "Hello 帧超限({} B > {MAX_FRAME_BYTES} B)", diff --git a/crates/df-storage/src/crud/idea_repo.rs b/crates/df-storage/src/crud/idea_repo.rs index 5c09fcb..208347b 100644 --- a/crates/df-storage/src/crud/idea_repo.rs +++ b/crates/df-storage/src/crud/idea_repo.rs @@ -404,7 +404,7 @@ impl IdeaRepo { /// 更新单字段,**仅作用于未软删记录**(`WHERE id AND deleted_at IS NULL`)。 /// - /// LW-6(BE-CMD-5):通用 [`update_field`] 不过滤软删(回收站灵感仍可改字段), + /// LW-6通用 [`update_field`] 不过滤软删(回收站灵感仍可改字段), /// 本方法收口软删防护,供命令层 `update_idea` 使用——软删灵感(回收站)返回 `false`, /// 调用方据此报「已删除」。字段名走同款 [`validate_column_name`] 白名单防注入。 pub async fn update_field_active(&self, id: &str, field: &str, value: &str) -> Result { @@ -430,7 +430,7 @@ impl IdeaRepo { /// 原子「立项认领」:CAS 写回 status=promoted + promoted_to(`WHERE id AND promoted_to IS NULL`)。 /// - /// LW-8(BE-CMD-7):promote_idea 读-改-写竞态的原子关闭。promote_idea 先建项目再回写灵感, + /// promote_idea 读-改-写竞态的原子关闭。promote_idea 先建项目再回写灵感, /// 双击/并发两次 promote 都读到 promoted_to=None → 各自建项目;回写时本方法用 /// `promoted_to IS NULL` 做 CAS——仅首个认领成功(affected=1),第二个 affected=0, /// 调用方据此判定「灵感已立项」并回滚自己刚建的项目(补偿删除),杜绝重复立项。 diff --git a/crates/df-storage/src/crud/project_repo.rs b/crates/df-storage/src/crud/project_repo.rs index 6ff8374..ca05f71 100644 --- a/crates/df-storage/src/crud/project_repo.rs +++ b/crates/df-storage/src/crud/project_repo.rs @@ -45,7 +45,7 @@ pub struct ProjectQuery { /// order_by 白名单(独立于 update_field 白名单,对齐 ideas 的 validate_idea_order_by 模式)。 /// -/// BE-CMD-3:projects update_field 白名单已剔除 id/created_at(主键与创建时间不可经通用 +/// projects update_field 白名单已剔除 id/created_at(主键与创建时间不可经通用 /// update_field 改写),但 `created_at` 作为**排序字段**仍合法——故排序白名单单独定义, /// 不依赖 update_field 白名单(否则 order_by=created_at 会被误拒,破坏 list_by_query 默认排序)。 const PROJECT_ORDER_BY_ALLOWED: &[&str] = &["created_at", "updated_at", "name", "status"]; @@ -387,7 +387,7 @@ impl ProjectRepo { /// 查找已绑定该规范化路径的项目(排除 exclude_id 自身)。无冲突返回 None。 /// - /// DRY(R-PD-11):统一 project.rs::find_binding_conflict 与 tool_registry.rs::bind_dir_to_project + /// DRY统一 project.rs::find_binding_conflict 与 tool_registry.rs::bind_dir_to_project /// 的「列项目→排除自身→按规范化路径比较」逻辑。本 crate 不依赖 df-project,故 norm_path 须由 /// 调用方先经 `df_project::scan::normalize_path`(内含 canonicalize,防 `C:\a\b` vs `C:/a/b/` 绕过)。 pub async fn find_path_conflict( @@ -464,7 +464,7 @@ impl ProjectRepo { /// 更新单字段,**仅作用于未软删记录**(`WHERE id AND deleted_at IS NULL`)。 /// - /// LW-6(BE-CMD-5):通用 [`update_field`] 不过滤软删(回收站项目仍可改字段), + /// LW-6通用 [`update_field`] 不过滤软删(回收站项目仍可改字段), /// 本方法收口软删防护,供命令层 `update_project` 使用——软删项目(回收站)返回 `false`, /// 调用方据此报「已删除」。字段名走同款 [`validate_column_name`] 白名单防注入。 pub async fn update_field_active(&self, id: &str, field: &str, value: &str) -> Result { diff --git a/crates/df-storage/src/crud/task_repo.rs b/crates/df-storage/src/crud/task_repo.rs index 094522c..187bb44 100644 --- a/crates/df-storage/src/crud/task_repo.rs +++ b/crates/df-storage/src/crud/task_repo.rs @@ -461,7 +461,7 @@ impl TaskRepo { /// 复用 list_by_query 的 WHERE 构造逻辑(仅 WHERE,无 ORDER BY/LIMIT), /// 返回满足条件的总行数(忽略分页裁剪)。 /// - /// LW-5(BE-CMD-2):补齐 assignee/queue/parent_id/module_id 维度,与 list_by_query + /// LW-5补齐 assignee/queue/parent_id/module_id 维度,与 list_by_query /// 全维度对齐——此前 count 缺四维导致「count 超算、list 空页」翻页不一致 /// (前端分页 total 与页数据对不上)。 pub async fn count_by_query(&self, query: &TaskQuery) -> Result { @@ -493,7 +493,7 @@ impl TaskRepo { where_clauses.push(format!("priority = ?{}", params_vec.len() + 1)); params_vec.push(Box::new(p)); } - // LW-5: assignee 维度(与 list_by_query 同 WHERE 构造,防 count/list 漂移) + // assignee 维度(与 list_by_query 同 WHERE 构造,防 count/list 漂移) if let Some(ref a) = assignee { where_clauses.push(format!("assignee = ?{}", params_vec.len() + 1)); params_vec.push(Box::new(a.clone())); @@ -507,7 +507,7 @@ impl TaskRepo { params_vec.push(Box::new(pat.clone())); params_vec.push(Box::new(pat)); } - // LW-5: queue / parent_id / module_id 维度(知识图谱 V29 + 工程 V41) + // queue / parent_id / module_id 维度(知识图谱 V29 + 工程 V41) if let Some(ref q) = queue { where_clauses.push(format!("queue = ?{}", params_vec.len() + 1)); params_vec.push(Box::new(q.clone())); @@ -815,7 +815,7 @@ impl TaskRepo { /// 更新单字段,**仅作用于未软删记录**(`WHERE id AND deleted_at IS NULL`)。 /// - /// LW-6(BE-CMD-5):通用 [`update_field`] 不过滤软删(回收站任务仍可改字段), + /// LW-6通用 [`update_field`] 不过滤软删(回收站任务仍可改字段), /// 本方法收口软删防护,供命令层 `update_task` 使用——软删任务(回收站)返回 `false`, /// 调用方据此报「已删除」,杜绝回收站任务被字段更新复活/改动。 /// 字段名走同款 [`validate_column_name`] 白名单(防注入 + 按表隔离)。 diff --git a/crates/df-storage/src/migrations.rs b/crates/df-storage/src/migrations.rs index bbd39ab..9b4a6e0 100644 --- a/crates/df-storage/src/migrations.rs +++ b/crates/df-storage/src/migrations.rs @@ -704,7 +704,7 @@ fn migrate_v26(conn: &Connection) -> Result<()> { Ok(()) } -/// V27: 审批状态统一 executed→completed(TD-260621-05) +/// V27: 审批状态统一 executed→completed /// /// chat.rs ai_approve/ai_authorize_dir 审批通过后工具执行成功历史写 "executed",audit 内联执行 /// (低风险无审批)写 "completed",双轨并存致:① DTO 文档(audit/mod.rs:53)只列 completed 契约失配; @@ -1880,7 +1880,7 @@ mod tests { assert_eq!(v, 20); } - /// V27:审批状态统一 executed→completed(TD-260621-05) + /// V27:审批状态统一 executed→completed /// 存量 executed 记录转 completed,rejected/completed/failed 不变(只动 executed) #[test] fn v27_unifies_executed_to_completed() { diff --git a/crates/df-types/src/events.rs b/crates/df-types/src/events.rs index 18836ea..dbea519 100644 --- a/crates/df-types/src/events.rs +++ b/crates/df-types/src/events.rs @@ -74,7 +74,7 @@ pub enum WorkflowEvent { }, /// 工作流执行完成 /// - /// B-03b-R10 ③(波17 治本): execution_id 字段标识本次终态事件归属的工作流执行。 + /// b-R10 ③(波17 治本): execution_id 字段标识本次终态事件归属的工作流执行。 /// 此前该变体不带 execution_id,AppState.event_bus 全局单例下并发工作流的终态事件 /// 会被 forward 循环的 matches! 完成判定(workflow.rs:159-163 只看变体不看 exec_id)误判, /// 致任一工作流终态触发所有 forward 循环 break(过早断他人链)。 @@ -86,7 +86,7 @@ pub enum WorkflowEvent { }, /// 工作流执行失败 /// - /// B-03b-R10 ③(波17 治本): execution_id 字段标识本次终态事件归属的工作流执行(同 WorkflowCompleted)。 + /// b-R10 ③(波17 治本): execution_id 字段标识本次终态事件归属的工作流执行(同 WorkflowCompleted)。 WorkflowFailed { #[serde(default)] execution_id: ExecutionId, diff --git a/crates/df-workflow/src/executor.rs b/crates/df-workflow/src/executor.rs index 6e94bdb..23bae8f 100644 --- a/crates/df-workflow/src/executor.rs +++ b/crates/df-workflow/src/executor.rs @@ -207,7 +207,7 @@ impl DagExecutor { Err(e) => { let error_msg = e.to_string(); // 已取消的节点(如 HumanNode 审批取消)跳过 set_failed: - // Cancelled 已是终态,transition(Cancelled→Failed) 非法会 bail 致工作流崩溃(B-03b-R1) + // Cancelled 已是终态,transition(Cancelled→Failed) 非法会 bail 致工作流崩溃(b-R1) if self.state_machine.is_cancelled(&node_id) { // emit NodeCancelled(非 NodeFailed):取消语义有别于失败,前端按 type 归「取消」非「失败」 self.event_bus @@ -239,7 +239,7 @@ impl DagExecutor { } let total = start.elapsed().as_millis() as u64; - // B-03b-R10 ③: WorkflowCompleted 携带 execution_id,供 forward 循环按 exec_id 匹配 + // b-R10 ③: WorkflowCompleted 携带 execution_id,供 forward 循环按 exec_id 匹配 // (全局 event_bus 单例下并发工作流的终态事件不再误触发他人 forward 的完成判定)。 self.event_bus .send(WorkflowEvent::WorkflowCompleted { diff --git a/crates/df-workflow/src/executor_helpers.rs b/crates/df-workflow/src/executor_helpers.rs index bdeb3ca..f4953e7 100644 --- a/crates/df-workflow/src/executor_helpers.rs +++ b/crates/df-workflow/src/executor_helpers.rs @@ -198,7 +198,7 @@ impl Node for CancelSelfNode { } } -/// B-03b-R1:取消的节点返回 Err 时,executor 跳过 set_failed(Cancelled→Failed transition 非法会 bail), +/// b-R1:取消的节点返回 Err 时,executor 跳过 set_failed(Cancelled→Failed transition 非法会 bail), /// 状态保持 Cancelled,run 返回取消相关 Err 而非状态转换错误。 #[tokio::test] async fn test_cancelled_node_skips_set_failed() { @@ -248,7 +248,7 @@ impl Node for CancelSelfThenOkNode { } } -/// R-P1-3:Ok 后取消的 TOCTOU 防护。节点返回 Ok 但执行期间已被 set_cancelled, +/// Ok 后取消的 TOCTOU 防护。节点返回 Ok 但执行期间已被 set_cancelled, /// executor Ok 分支必须跳过 set_completed(transition Cancelled→Completed 非法会 bail), /// 状态保持 Cancelled,run 返回 Ok(已批准审批不应误报失败)。 #[tokio::test] diff --git a/docs/todo.md b/docs/todo.md index 4e4fa96..9d4be81 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -771,7 +771,7 @@ graph TD --- **🟡 P1**: -- [ ] **SC-260811-P1-1** — 状态机最短路径推导:deferred→blocked 需 4 跳,AI 靠试错 47 次 advance_task。**建议**:错误信息附最短合法路径,或提供一步跳转(内部串多跳) +- [x] **SC-260811-P1-1** ✅ 已销账(2026-08-15 核验,1b012bc 覆盖):`task_state_machine.rs:155 shortest_path` BFS 推导 + `task_advance_node.rs:81-84` 非法转换错误信息附加最短路径提示(路径>2 跳时),+6 单测 **🔧 既有测试失败修复(2026-08-11)**:累计修 7 项既有失败,df-nodes 175 全过 / df-storage 166 全过: - ✅ `ai_self_review_node::clamp_dimension_scores_in_place` 类型保真:区间内整数保持整数(9 不转 9.0),仅越界 clamp——修 parse_review_json_valid_passes_through,同步调 clamps_out_of_range_scores 边界值断言(10 保持整数) diff --git a/src-tauri/src/commands/ai/agentic/context_lifecycle.rs b/src-tauri/src/commands/ai/agentic/context_lifecycle.rs index 9ba6cb0..ba63c8e 100644 --- a/src-tauri/src/commands/ai/agentic/context_lifecycle.rs +++ b/src-tauri/src/commands/ai/agentic/context_lifecycle.rs @@ -165,7 +165,7 @@ pub(super) async fn maybe_auto_compress( String::new() }; - // L0-3:保护区外无 active 可压缩(active_msgs 空)→ noop,同步复位 is_compressing + // 保护区外无 active 可压缩(active_msgs 空)→ noop,同步复位 is_compressing // 后返回(不 spawn 空任务)。对齐原 Ok(None) 分支语义。 if active_msgs.is_empty() { session_arc.lock().await.conv(&conv_id).messages.set_compressing(false); diff --git a/src-tauri/src/commands/ai/agentic/mod.rs b/src-tauri/src/commands/ai/agentic/mod.rs index 7e62e1b..923d0da 100644 --- a/src-tauri/src/commands/ai/agentic/mod.rs +++ b/src-tauri/src/commands/ai/agentic/mod.rs @@ -277,7 +277,7 @@ pub const CIRCUIT_BREAKER_THRESHOLD: u32 = 3; /// 单轮并行失败爆发,又足够长以容忍偶发抖动。 pub const CIRCUIT_BREAKER_WINDOW: usize = 20; -/// BE2(AC-EFF-R2-1):断路器/探索熔断读消息的尾部窗口大小。 +/// BE2 断路器/探索熔断读消息的尾部窗口大小。 /// /// count_recent_failures / check_stall_breaker 只关心最近若干条消息(末尾工具结果 + 其前驱 /// assistant tool_calls / 最近几轮工具签名),用 ContextManager::recent_messages 只 clone 尾部 @@ -570,7 +570,7 @@ async fn stream_with_retry( /// 表达本轮的 4 种结束路径,供 loop 主框架据以 `return` 或继续后续处理。 /// /// **不在本函数范围**(loop 内其它段): -/// - 停止/收敛/审批等待/熔断(L1815-L1917):这些在 process_tool_calls 之后, +/// - 停止/收敛/审批等待/熔断这些在 process_tool_calls 之后, /// 属「工具执行后路径」,与流式调用本身解耦,留 loop 主体。 /// - 正常完成(loop 外 L1957+):loop 退出后由 run_agentic_loop 直收尾。 enum RoundOutcome { @@ -1096,12 +1096,12 @@ pub(crate) async fn run_agentic_loop( // 单 provider 场景:池仅 1 enabled provider → select 返回单元素 Vec → 首位 = 唯一 provider, // 行为同 F-01 前(零变化)。空池(0 enabled)→ fallback 入参 provider_config(保启动行为)。 // - // F-04b:select 返回的**完整有序 Vec** 即 fallback 序(主→备用)。本轮接入真实切换: + // b:select 返回的**完整有序 Vec** 即 fallback 序(主→备用)。本轮接入真实切换: // 流式重试块外层包 `for candidate in &candidates`,主 candidate InitFailed{retryable=true} // 耗尽 → continue 切下一 candidate(stream_one_provider 内重建 build_provider + resolved_model // 在新 candidate.model_configs 上重算 select_model_id);Fatal(4xx 非429)立即放弃整轮。 // - // 主候选的 model_configs 用于路由(F-01),其 provider_config 用于 build_provider。 + // 主候选的 model_configs 用于路由,其 provider_config 用于 build_provider。 // compress_via_llm / 标题 / 后台 spawn 沿用主 candidate(非 fallback 范围,见各调用点注释)。 // // AiProviderRepo::new 仅 clone Arc(廉价),不复用 AppState.ai_providers @@ -1212,7 +1212,7 @@ pub(crate) async fn run_agentic_loop( } }; // 用主候选覆盖入参 provider_config(下游 build_provider / 路由 / 日志均用此)。 - // mut:F-04b 切换 candidate 后更新为实际成功所用 provider(供后续 push/save/标题 spawn)。 + // mut:b 切换 candidate 后更新为实际成功所用 provider(供后续 push/save/标题 spawn)。 let mut provider_config = primary_provider; // 诊断日志:401/错误时据此定位是 url/type/model/key 哪项问题(只记长度不记明文) tracing::info!( @@ -1250,7 +1250,7 @@ pub(crate) async fn run_agentic_loop( // 用户指定模型 override 穿透(仅主对话生效,标题/扫描/灵感仍走路由)。 // 兜底原则:override 非空且在该 provider model_configs 池中 → 用 override;否则用 resolved_model。 // 绝不让 override 导致无模型(空/不在池 → 落回路由结果,行为不变)。 - // mut:F-04b 切换 candidate 后由 stream_one_provider 返回的 Success.resolved_model 覆盖 + // mut:b 切换 candidate 后由 stream_one_provider 返回的 Success.resolved_model 覆盖 // (新 candidate 模型池不同,必须重选);此后 push/save 均用迭代最新值。 let mut resolved_model = match model_override.as_deref() { Some(id) if !id.is_empty() @@ -2238,7 +2238,7 @@ async fn count_recent_failures( conv_id: &str, fail_window: &mut std::collections::VecDeque, ) -> (u32, String) { - // BE2(AC-EFF-R2-1):只克隆尾部 BREAKER_TAIL_N 条,避免 all_messages_clone 全量深克隆 + // BE2只克隆尾部 BREAKER_TAIL_N 条,避免 all_messages_clone 全量深克隆 // (长对话每轮 O(n) clone 浪费)。窗口覆盖末尾 Tool 结果与其前驱 assistant tool_calls // (同轮工具回填通常 1~5 条,40 足富余;超窗工具映射退化为 unknown_tool,仅失败分组更粗,不误熔断)。 let messages = { @@ -2298,7 +2298,7 @@ async fn count_recent_failures( /// /// - run_command 等带 exit_code → `tool_name::exit=N`(非零退出码即失败类别)。 /// - status=failed/error envelope → `tool_name::error:<错误文本稳定哈希>`: -/// 用 fxhash 风格简单 FNV-1a 哈希避免裸截内容前缀(前 40 字符易把同类失败拆多 key +/// 用 fxhash 风格简单 a 哈希避免裸截内容前缀(前 40 字符易把同类失败拆多 key /// 或异类误聚)。哈希值非敏感信息(仅断路器分组用),无碰撞顾虑(N=20 窗口内几无碰撞)。 /// - 内容解析失败(非 JSON)→ is_failure_content 已返回 false 不会进本函数, /// 此处兜底返回 `tool_name::unknown` 防御。 @@ -2327,7 +2327,7 @@ fn failure_key(tool_name: &str, content: &str) -> String { format!("{}::{}:{:08x}", tool_name, bucket, h) } -/// FNV-1a 32-bit 哈希(零依赖,纯函数,断路器 key 归一专用;非密码学用途)。 +/// a 32-bit 哈希(零依赖,纯函数,断路器 key 归一专用;非密码学用途)。 fn fnv1a_32(bytes: &[u8]) -> u32 { let mut hash: u32 = 0x811c9dc5; for &b in bytes { @@ -2556,7 +2556,7 @@ async fn check_stall_breaker( // arguments 非合法 JSON 时 to_string 兜底(签名仍可比,只是兜底全量)。 let signatures: Vec = { let session = session_arc.lock().await; - // BE2(AC-EFF-R2-1):只取尾部 BREAKER_TAIL_N 条(签名抽取只需最近几轮工具调用, + // BE2只取尾部 BREAKER_TAIL_N 条(签名抽取只需最近几轮工具调用, // 全量 clone 纯浪费;STALL_BREAKER_SAMPLE_SIZE=12 ≪ 40,样本充足)。 let messages = match session.conv_read(conv_id) { Some(conv) => conv.messages.recent_messages(BREAKER_TAIL_N), diff --git a/src-tauri/src/commands/ai/audit/approval.rs b/src-tauri/src/commands/ai/audit/approval.rs index 770e40e..eed036d 100644 --- a/src-tauri/src/commands/ai/audit/approval.rs +++ b/src-tauri/src/commands/ai/audit/approval.rs @@ -28,7 +28,7 @@ use super::super::{AiChatEvent, AiSession, ApprovalKind, PendingApproval, ToolCa /// - medium:Low+Medium 自动 /// - all:全自动无审批(完全 AI 接管) /// -/// C-260627:patch_file 小改动(≤5 行)特例自动放行,不阻塞 AI 工作流。 +/// patch_file 小改动(≤5 行)特例自动放行,不阻塞 AI 工作流。 pub(super) fn classify_risk_and_auto( risk_level: RiskLevel, auto_exec_mode: &str, diff --git a/src-tauri/src/commands/ai/audit/cache.rs b/src-tauri/src/commands/ai/audit/cache.rs index 105a6b1..40e7962 100644 --- a/src-tauri/src/commands/ai/audit/cache.rs +++ b/src-tauri/src/commands/ai/audit/cache.rs @@ -96,7 +96,7 @@ fn resolve_namespace_content(session: &AiSession, content: &str) -> String { /// tool_result 回传 LLM → LLM(不可靠)仍重试同命令 → 新 tool_call_id(provider 每轮新 id) /// → `process_tool_calls` 重新 insert pending(audit.rs:418) → 用户被迫重新审批,循环。 /// -/// **治本(F-05)**:即使 LLM 仍重试,同 (tool_name, args) 不重复审批。本函数扫描会话历史, +/// **治本**:即使 LLM 仍重试,同 (tool_name, args) 不重复审批。本函数扫描会话历史, /// 若发现**已落定**(executed/failed/rejected,非 pending)的同类同参高危调用,返回其 /// tool_result 内容,调用方把缓存结果作为新 tool_call_id 的 tool_result 回传 LLM,跳过审批。 /// @@ -216,7 +216,7 @@ fn canonical_args_key(args: &serde_json::Value) -> String { /// 后续命中返回缓存 + 专用警告(cache_hit_warning 目录分支)。 /// 其他只读工具仍走全参 JSON 归一(键序无关,见 canonical_args_key)。 /// -/// pub(crate):供 audit/mod.rs 轮内去重(AC-EFF-T1-2)复用同一 key 口径(同源同 key, +/// pub(crate):供 audit/mod.rs 轮内去重复用同一 key 口径(同源同 key, /// 跨轮缓存 + 轮内去重判定一致,防两处 key 漂移致去重误判)。 pub(crate) fn readonly_cache_args_key(tool_name: &str, args: &serde_json::Value) -> String { if matches!(tool_name, "list_directory" | "list_dir") { diff --git a/src-tauri/src/commands/ai/audit/data_change.rs b/src-tauri/src/commands/ai/audit/data_change.rs index ce67fcf..e52e5e1 100644 --- a/src-tauri/src/commands/ai/audit/data_change.rs +++ b/src-tauri/src/commands/ai/audit/data_change.rs @@ -25,7 +25,7 @@ pub(super) fn data_change_for_tool(name: &str) -> Option<(&'static str, &'static "update_project" => ("project", "update"), "update_task" => ("task", "update"), "update_idea" => ("idea", "update"), - // P0-2:advance_task 改任务状态 = 任务数据变更,MCP 走 HTTP 形态时让 GUI 前端 + // advance_task 改任务状态 = 任务数据变更,MCP 走 HTTP 形态时让 GUI 前端 // listen df-data-changed 刷新任务列表(此前不在映射 → 状态推进后 GUI 不刷新)。 // 数据变更机制是 entity 级(无 id),映射到 (task, update) 对齐 update_task。 "advance_task" => ("task", "update"), diff --git a/src-tauri/src/commands/ai/audit/mod.rs b/src-tauri/src/commands/ai/audit/mod.rs index 031f41b..cf3d986 100644 --- a/src-tauri/src/commands/ai/audit/mod.rs +++ b/src-tauri/src/commands/ai/audit/mod.rs @@ -536,7 +536,7 @@ pub(crate) async fn process_tool_calls( }; let _ = app_clone.emit("ai-chat-event", ev.clone()); let _ = app_clone.state::().ai_event_bus.publish_event(ev); - // AR-11:数据变更类工具执行成功后 emit df-data-changed 联动刷新 + // 数据变更类工具执行成功后 emit df-data-changed 联动刷新 // (write_file/run_command 不在 data_change_for_tool 映射内,emit_data_changed 内部 None 即 noop) emit_data_changed(&app_clone, &draft.name); (draft, risk_level, Ok(content)) diff --git a/src-tauri/src/commands/ai/audit/reason.rs b/src-tauri/src/commands/ai/audit/reason.rs index 98c2292..d4b1bfa 100644 --- a/src-tauri/src/commands/ai/audit/reason.rs +++ b/src-tauri/src/commands/ai/audit/reason.rs @@ -14,7 +14,7 @@ use df_storage::db::Database; /// 查项目可读标签:id → "「项目名」(id=x)"。 /// -/// AR-3:审批卡片需显示对象名而非裸 id(用户反馈"只返回 ID 不知道是什么数据")。 +/// 审批卡片需显示对象名而非裸 id(用户反馈"只返回 ID 不知道是什么数据")。 /// 三臂区分: /// - Ok(Some) → 项目名标签 /// - Ok(None) → "项目已不存在"(真不存在,项目已彻底清除/外部 id) diff --git a/src-tauri/src/commands/ai/audit/restore.rs b/src-tauri/src/commands/ai/audit/restore.rs index aa3719c..9ae13a0 100644 --- a/src-tauri/src/commands/ai/audit/restore.rs +++ b/src-tauri/src/commands/ai/audit/restore.rs @@ -21,7 +21,7 @@ use super::risk_from_str; /// **惰性建 `PerConvState`**(不依赖 `active_conversation_id` 单值),使后续 ai_approve → /// try_continue_agent_loop 的 `conv_read(conv_id)` 命中各自 per_conv(各归各,不串)。 /// `pending_approvals` 本身保持单层 HashMap(设计 §2.1.2,已带 conversation_id 是业务 -/// 语义而非路由键)。conversation_id=None 的无主审批(R-9)不建 per_conv(无 conv_id 可挂), +/// 语义而非路由键)。conversation_id=None 的无主审批不建 per_conv(无 conv_id 可挂), /// 仍进 pending_approvals 单层表,后续审批按 tool_call_id 路由,不影响正确性。 pub async fn restore_pending_approvals(state: &AppState) { // 清理超 24 小时的残留 pending(旧会话遗留,不再有意义) @@ -64,7 +64,7 @@ pub async fn restore_pending_approvals(state: &AppState) { continue; } // 多 conv 分配:有 conversation_id 的审批惰性建/复用对应 conv 的 per_conv(设计 §3 batch8)。 - // 无 conversation_id 的无主审批(R-9)不建 per_conv——无 conv_id 可挂,仍进 pending_approvals + // 无 conversation_id 的无主审批不建 per_conv——无 conv_id 可挂,仍进 pending_approvals // 单层表按 tool_call_id 路由,后续 ai_approve 路径不依赖其 per_conv。 if let Some(cid) = rec.conversation_id.as_deref() { if !cid.is_empty() && convs_restored.insert(cid.to_string()) { @@ -133,7 +133,7 @@ mod tests_multi_conv_restore { ("tc-1".to_string(), "write_file".to_string(), Some("conv-a".to_string())), ("tc-2".to_string(), "run_command".to_string(), Some("conv-a".to_string())), ("tc-3".to_string(), "delete_project".to_string(), Some("conv-b".to_string())), - ("tc-4".to_string(), "create_task".to_string(), None), // 无主(R-9) + ("tc-4".to_string(), "create_task".to_string(), None), // 无主 ]; // 复现 restore_pending_approvals 的分配逻辑(同名变量,逐字对齐实现)。 diff --git a/src-tauri/src/commands/ai/command_stream.rs b/src-tauri/src/commands/ai/command_stream.rs index 0379c0b..0f0a83a 100644 --- a/src-tauri/src/commands/ai/command_stream.rs +++ b/src-tauri/src/commands/ai/command_stream.rs @@ -110,7 +110,7 @@ impl CommandSink { /// 累积一行 + 触发 flush 判定(行加入 stdout/stderr 分桶)。 /// - /// AC-EFF-S2-5:不立即 emit,先累积入缓冲;体积超阈值立即 flush,否则等 50ms 定时 flush + /// 不立即 emit,先累积入缓冲;体积超阈值立即 flush,否则等 50ms 定时 flush /// 或 Drop 兜底 flush。emit 失败静默吞(前端未 listen / 总线无订阅不阻断命令执行)。 fn emit(&self, kind: StreamKind, line: &str) { let over_threshold = { diff --git a/src-tauri/src/commands/ai/commands/chat.rs b/src-tauri/src/commands/ai/commands/chat.rs index e01637f..bcd858c 100644 --- a/src-tauri/src/commands/ai/commands/chat.rs +++ b/src-tauri/src/commands/ai/commands/chat.rs @@ -45,7 +45,7 @@ use super::super::{AiChatEvent, ApprovalKind, SessionState}; /// 把当前所有挂起审批的占位 tool_result(内容为 audit.rs:PENDING_APPROVAL_PLACEHOLDER /// "需要用户审批,等待确认")就地替换为终态文本。 /// -/// 背景(SW-260618-02):审批占位文本在 stop/clear/create/delete/force_send 等 +/// 背景审批占位文本在 stop/clear/create/delete/force_send 等 /// 清 pending_approvals 时若不替换,会残留在 messages 里,下次发送把占位喂给 LLM。 /// 占位内容非工具结果语义,污染上下文 + 误导模型继续等待。 /// @@ -60,7 +60,7 @@ use super::super::{AiChatEvent, ApprovalKind, SessionState}; /// conv_id 入参作 fallback(审批无 conversation_id 的无主审批 R-9 异常数据,终态化到入参 conv, /// 入参也空则跳过——审计仍记,占位不残留内存因 pending 即将被 clear/retain)。 pub(crate) fn finalize_pending_placeholders(session: &mut super::super::AiSession, conv_id: &str, final_text: &str) { - // SW-260618-02: 先 clone pending 的 tool_call_id(借用在此结束),再可变借 messages。 + // 先 clone pending 的 tool_call_id(借用在此结束),再可变借 messages。 // 必须整体借 &mut session 在函数体内做 disjoint field borrow —— 调用方若分别传 // &mut messages + &pending 两个引用,函数参数列表不做 disjoint 推断会触发 E0502。 // 单真相源合并:两类挂起(path + risk)合一进 pending_approvals,占位都需终态化。 @@ -258,7 +258,7 @@ fn append_volatile(volatile: String, seg: &str) -> String { // 发送 / 审批 / 控制 // ============================================================ -/// 重新生成最后一条 AI 回复(UX-02:消息操作栏「重新生成」) +/// 重新生成最后一条 AI 回复消息操作栏「重新生成」) /// /// 流程:占用 generating → 弹出末尾 AI 回复(pop_last_assistant_round,保留触发它的 /// user 消息)→ save 落库(避免前端切走时残留旧回复)→ spawn run_agentic_loop 重跑 @@ -368,9 +368,9 @@ pub async fn ai_regenerate( let app_handle = app.clone(); let knowledge_config = state.knowledge_config.lock().await.clone(); let llm_concurrency = state.llm_concurrency.clone(); - // F-260616-01: loop 入口 load 快照,当前 loop 锁定边界(热改下次发消息生效) + // loop 入口 load 快照,当前 loop 锁定边界(热改下次发消息生效) let max_iterations = state.agent_max_iterations.load(Ordering::SeqCst); - // F-260616-07: 流式失败重试次数快照 + // 流式失败重试次数快照 let max_retries = state.agent_max_retries.load(Ordering::SeqCst); // F1 并发 epoch(F-260616-09 决策 e 真并发上线):新 loop 的 owner token。 // 每 spawn 前 fetch_add(1) 递增,旧 loop 即刻失效(退出/Guard 复位/emit 只认自己 epoch)。 @@ -386,7 +386,7 @@ pub async fn ai_regenerate( Ok("ok".to_string()) } -/// 查询后端真实 generating 状态(B-260615-22:方案 A 发送前 IPC 查后端真值) +/// 查询后端真实 generating 状态方案 A 发送前 IPC 查后端真值) /// /// 前端 `state.streaming` 与后端 `AiSession.generating` 各自维护: /// 后端 loop 异常退出 / AiError 已复位 generating=false 时,前端若仅查本地 streaming @@ -606,7 +606,7 @@ pub async fn ai_chat_send( // 批3 双轨收口:generating bool 已退役,生成态由 run_agentic_loop 入口迁移设置(不再手动赋值)。 conv.stop_flag.store(false, Ordering::SeqCst); conv.agent_language = language.clone(); - // F-260616-11: 新对话生命周期 iteration 从头计数(累计计数器复位)。 + // 新对话生命周期 iteration 从头计数(累计计数器复位)。 conv.iteration_used = 0; // F-01 阶段6: 记录用户指定模型 override(主对话专用)。run_agentic_loop 兜底校验 // (override 非空且在 provider model_configs 池中才用,否则落回路由结果)。 @@ -710,9 +710,9 @@ pub async fn ai_chat_send( let app_handle = app.clone(); let knowledge_config = state.knowledge_config.lock().await.clone(); let llm_concurrency = state.llm_concurrency.clone(); - // F-260616-01: loop 入口 load 快照,当前 loop 锁定边界(热改下次发消息生效) + // loop 入口 load 快照,当前 loop 锁定边界(热改下次发消息生效) let max_iterations = state.agent_max_iterations.load(Ordering::SeqCst); - // F-260616-07: 流式失败重试次数快照 + // 流式失败重试次数快照 let max_retries = state.agent_max_retries.load(Ordering::SeqCst); // F1 并发 epoch(F-260616-09 决策 e 真并发上线):新 loop 的 owner token。 // 每 spawn 前 fetch_add(1) 递增,旧 loop 即刻失效(退出/Guard 复位/emit 只认自己 epoch)。 @@ -745,8 +745,8 @@ pub async fn ai_approve( // 阶段3a 单真相源合并:ai_approve 从单 pending_approvals remove(原 risk_pending 合一)。 // 若前端误调 ai_approve 消费 path_auth 挂起 → 这里拿不到该 id(kind=Path 的挂起也走同一表, // 但下方 kind 校验会拦截 Err)。无挂起则查审计表(幂等,已处理返成功)。 - // F-260616-06: 幂等——内存无挂起审批时查审计表,若已处理则返回成功(非报错) - // BUG-260618-11: 区分 Err(DB 故障)与 Ok(None)(真无记录),原 unwrap_or_default 把 + // 幂等——内存无挂起审批时查审计表,若已处理则返回成功(非报错) + // 区分 Err(DB 故障)与 Ok(None)(真无记录),原 unwrap_or_default 把 // Err 压成 None,DB 故障被「未找到」误导(实为可重试故障),对齐 audit.rs audit_finalize 三路分流。 let audit_status = match state.ai_tool_executions.find_by_tool_call_id(&tool_call_id).await { Ok(Some(rec)) => { @@ -799,7 +799,7 @@ pub async fn ai_approve( // 无 conversation_id 的无主审批(R-9 异常数据)无 per_conv 可写,拒绝结果不入 messages // (审计仍记 rejected),避免写错 conv。 if let Some(ref cid) = approval.conversation_id { - // BUG-2026-07-07/P1-3:拒绝消息改结构化 JSON,防裸文本破坏 tool role 消息协议 + // BUG-2026-07-07/拒绝消息改结构化 JSON,防裸文本破坏 tool role 消息协议 // (部分 provider 严格解析 tool role content,裸文本会触发协议错误)。 let reject_payload = serde_json::json!({ "status": "rejected", @@ -1184,7 +1184,7 @@ pub async fn ai_authorize_dir( // ── "deny":拒绝路径授权 → 工具返 Err,恢复 loop ── if decision == "deny" { - // BUG-2026-07-07/P1-3:拒绝消息改结构化 JSON,防裸文本破坏 tool role 消息协议 + // BUG-2026-07-07/拒绝消息改结构化 JSON,防裸文本破坏 tool role 消息协议 let err_msg = serde_json::json!({ "status": "rejected", "reason": "path_auth_denied", @@ -1460,7 +1460,7 @@ pub async fn ai_chat_clear(state: State<'_, AppState>) -> Result<(), String> { // ── ③ 内存清(DB 成功后;内存 clear 不失败,回滚只需保证 DB 原子)── if let Some(ref id) = active_id { let mut session = state.ai_session.lock().await; - // SW-260618-02:清 pending 前先把占位 tool_result 终态化(此处 messages 紧随即 clear, + // 清 pending 前先把占位 tool_result 终态化(此处 messages 紧随即 clear, // 调用为 no-op,但保持清理点统一口径防顺序变更时占位随 messages 残留 DB)。 // F-260616-09 B 批4:per_conv.messages 唯一真相源,删顶层 messages.clear 双写; // pending_approvals 仅清 active conv 的(retain 保留其他 conv 的审批)。 @@ -1712,7 +1712,7 @@ pub async fn ai_chat_compress_context( Ok(()) } -/// 编辑最后一条 user 消息并重新生成(UX-09) +/// 编辑最后一条 user 消息并重新生成 /// /// 流程(复用 ai_regenerate 的 spawn 模式):占用 generating → 校验活跃对话一致 → /// ① 替换末条 active user 消息 content=new_message → ② 其后所有消息标 truncated(软删, @@ -1752,7 +1752,7 @@ pub async fn ai_chat_edit( // 批3 双轨收口:generating bool 已退役,生成态由 run_agentic_loop 入口迁移设置(不再手动赋值)。 conv.stop_flag.store(false, Ordering::SeqCst); conv.agent_language = language.clone(); - // F-260616-11: 编辑重生成 = 新生命周期起点,iteration 从头计数。 + // 编辑重生成 = 新生命周期起点,iteration 从头计数。 conv.iteration_used = 0; // F-01 阶段6: 记录用户指定模型 override(主对话专用)。 conv.model_override = model_override.clone(); @@ -1834,14 +1834,14 @@ pub async fn ai_chat_edit( Ok("ok".to_string()) } -/// 强制发送消息(B-260616-02: L2 发送韧性;BUG-260617-04: 原子复位+占用消除双锁竞态) +/// 强制发送消息L2 发送韧性;原子复位+占用消除双锁竞态) /// /// 当后端 generating=true 残留(HMR/异常退出等)导致 sendMessage 被拦截时, /// 前端可调此命令强制复位 generating + 清审批,然后发送新消息。 /// /// 旧实现先 lock 复位 generating→释放锁→调 ai_chat_send 再 lock,两锁之间另一个发送 /// IPC(force_send/send)可插入占用 generating=true → ai_chat_send 因 generating=true 被 -/// reject → 前端以为已强制发送但后端拒绝,状态不一致(BUG-260617-04)。 +/// reject → 前端以为已强制发送但后端拒绝,状态不一致。 /// /// 修法:把"软停止复位 + 新消息占用 generating"合并到**同一把锁**,消除竞态窗口; /// 其后 system prompt 构建 + spawn run_agentic_loop 与 ai_chat_send 同款(照 ai_chat_edit @@ -1907,7 +1907,7 @@ pub async fn ai_chat_force_send( // was_gen 读点(B-Phase2):改读无锁 conv_states.is_active()(Generating/Compressed), // 用于判断「原是否在生成」以决定是否 emit AiCompleted。语义等价。 let was_gen = state.conv_states.is_active(&target); - // SW-260618-02:强制发送丢弃目标 conv 的旧审批,先终态化占位 tool_result,防占位随 messages + // 强制发送丢弃目标 conv 的旧审批,先终态化占位 tool_result,防占位随 messages // 残留下次发送喂给 LLM。仅清目标 conv 的 pending_approvals(retain 保留其他 conv)。 finalize_pending_placeholders(&mut *session, &target, "已取消"); // 阶段3a 单真相源合并:单表 retain(仅清目标 conv,保留其他 conv,kind 不区分)。 @@ -1936,7 +1936,7 @@ pub async fn ai_chat_force_send( // 批3 收口:生成态由 run_agentic_loop 入口 ConvState→Generating 迁移设置(不再手动赋值)。 // 注:stop_flag 保持 true(旧 loop 退出用),新 loop spawn 前由下方锁外等待窗口后置 false。 conv.agent_language = language.clone(); - // F-260616-11: 强制发送 = 新对话生命周期起点,iteration 从头计数。 + // 强制发送 = 新对话生命周期起点,iteration 从头计数。 conv.iteration_used = 0; conv.model_override = model_override.clone(); // user content 处理(技能调用空文本标记 /{skillname}),照 ai_chat_send @@ -2109,7 +2109,7 @@ pub async fn ai_chat_stop( let has_pending = session.session_state(&target, &state.conv_states) == SessionState::AwaitingApproval; if has_pending { // 审批等待态:loop 已退出,直接清理目标 conv 让其立即可用 - // SW-260618-02:messages 不 clear,占位 tool_result 会残留,下次发送喂给 LLM。 + // messages 不 clear,占位 tool_result 会残留,下次发送喂给 LLM。 // 清 pending 前先终态化为"会话已停止"。仅清目标 conv 的 pending(retain 保留其他 conv)。 finalize_pending_placeholders(&mut *session, &target, "会话已停止"); session.pending_approvals.retain(|_, a| a.conversation_id.as_deref() != Some(target.as_str())); @@ -2152,7 +2152,7 @@ pub async fn ai_chat_stop( conv.stop_flag.store(true, Ordering::SeqCst); conv.notify.clone() }; - // B-260615-14:置位后立即 notify_one 唤醒阻塞在 stream.next() 的 select!, + // 置位后立即 notify_one 唤醒阻塞在 stream.next() 的 select!, // 不再等 30s 心跳 tick 或 120s idle timeout 才轮到 stop_flag 检查。 // Notify 仅承载「即时唤醒」,停止真值仍由 stop_flag 决定(stream_llm 唤醒后再判 flag)。 notify.notify_one(); @@ -2261,7 +2261,7 @@ pub async fn ai_chat_stop( Ok(()) } -/// 续跑 agentic 循环(F-260616-03:达 max_iterations 暂停态用户点「继续」) +/// 续跑 agentic 循环达 max_iterations 暂停态用户点「继续」) /// /// 场景:run_agentic_loop 达 max_iterations 未收敛 → emit AiMaxRoundsReached + 保持 /// generating=true 暂停。用户点继续调本命令 → 复位 stop_flag(防上一轮残留致续跑入口即退出) @@ -2308,7 +2308,7 @@ pub async fn ai_continue_loop( Ok("ok".to_string()) } -/// 停止 agentic 循环并走完成流程(F-260616-03:达 max_iterations 暂停态用户点「停止」) +/// 停止 agentic 循环并走完成流程达 max_iterations 暂停态用户点「停止」) /// /// 场景:run_agentic_loop 达 max_iterations 未收敛 → emit AiMaxRoundsReached + 保持 /// ConvState=Generating 暂停。用户点停止调本命令 → 复位生成态(ConvState→Idle) + emit AiCompleted(标收敛)。 diff --git a/src-tauri/src/commands/ai/commands/conversation.rs b/src-tauri/src/commands/ai/commands/conversation.rs index 8b92127..5e95a5d 100644 --- a/src-tauri/src/commands/ai/commands/conversation.rs +++ b/src-tauri/src/commands/ai/commands/conversation.rs @@ -176,7 +176,7 @@ pub async fn ai_conversation_create( let now = now_millis(); // F-260616-09 B 批4(决策 e 真并发上线):新建会话**不杀旧 loop**。 - // 旧实现(B-260615-10)生成中强制结束旧 conv 的 loop + clear 其 per_conv,在真并发下会误杀 + // 旧实现生成中强制结束旧 conv 的 loop + clear 其 per_conv,在真并发下会误杀 // 后台跑着的 conv。决策 e:旧 conv 的 per_conv 完整保留(后台 loop 继续跑自己的 conv), // 仅切换 active_conversation_id 到新会话 + 为新会话建独立 per_conv。 // @@ -682,7 +682,7 @@ pub async fn ai_conversation_archive( Ok(()) } -/// 置顶/取消置顶对话(UX-17:对话置顶) +/// 置顶/取消置顶对话对话置顶) /// /// 置顶后侧栏排序置前(前端按 pinned DESC, updated_at DESC)。 /// 纯元数据标记(同归档),不改 updated_at(保持相对时间不变)。 @@ -727,7 +727,7 @@ pub async fn ai_update_conversation_goals( Ok(()) } -/// 导出对话为指定格式(UX-18:对话导出) +/// 导出对话为指定格式对话导出) /// /// - 优先落库 messages(完整历史,与 switch 一致),内存 session 不读(可能被切走/未落库) /// - markdown: `## 用户` / `## 助手` 交替标题 + content 原样输出 diff --git a/src-tauri/src/commands/ai/commands/provider.rs b/src-tauri/src/commands/ai/commands/provider.rs index 57f363b..4b14009 100644 --- a/src-tauri/src/commands/ai/commands/provider.rs +++ b/src-tauri/src/commands/ai/commands/provider.rs @@ -13,7 +13,7 @@ //! 再经 `ai/mod.rs` 的 `pub use self::commands::*;` 透传到 `commands::ai::*`, //! 保 `lib.rs` invoke_handler + 前端 `api/ai.ts` 零改动。 //! -//! 纯搬迁:不改 IPC 签名/行为(FR-S1 密钥迁移 / F-260614-04c 池配置编辑保留 / F-01 阶段5 探测 +//! 纯搬迁:不改 IPC 签名/行为(FR-S1 密钥迁移 / c 池配置编辑保留 / F-01 阶段5 探测 //! 等逻辑原样保留)。 use tauri::State; @@ -37,7 +37,7 @@ use super::super::secret::{ // 提供商管理 // ============================================================ -/// api_key 脱敏:IPC 不传明文给前端(FR-S1),保留首尾各 4 字符便于辨识。 +/// api_key 脱敏:IPC 不传明文给前端,保留首尾各 4 字符便于辨识。 /// /// `pub(crate)` 供 get_app_config AI 工具复用(查 DevFlow 自身 provider 配置时同样脱敏, /// 不向 LLM 回灌明文 key,与 ai_list_providers IPC 走同一套脱敏规则,单点维护)。 @@ -83,7 +83,7 @@ pub async fn ai_list_providers(state: State<'_, AppState>) -> Result state.ai_providers.get_by_id(pid).await.map_err(err_str)?, @@ -125,13 +125,13 @@ pub async fn ai_save_provider( .map_err(err_str)? .iter().any(|p| p.is_default), }; - // F-260614-04c: enabled/weight 编辑保留原值(前端 Settings 改值经此落库); + // c: enabled/weight 编辑保留原值(前端 Settings 改值经此落库); // 新建默认进池(enabled=true,weight=50)。 let (enabled, weight) = match &existing { Some(p) => (p.enabled, p.weight), None => (true, 50), }; - // FR-S1:密钥存 OS keyring,DB api_key 列恒空(不入明文)。 + // 密钥存 OS keyring,DB api_key 列恒空(不入明文)。 // api_key 非空 = 新/改密钥 → 写 keyring;空 = 编辑不改 → 保留原 keyring 密钥不动。 let provider_id = id.clone().unwrap_or_else(new_id); if !api_key.is_empty() { @@ -140,7 +140,7 @@ pub async fn ai_save_provider( return Err(format!("密钥保存到系统钥匙串失败: {}", e)); } } else if let Some(pid) = &id { - // 空 key 编辑:保住密钥,防未迁移态静默丢失(R-PD-1)。 + // 空 key 编辑:保住密钥,防未迁移态静默丢失。 // 未迁移态(DB 有明文 + keyring 空)下,下方 INSERT OR REPLACE 会无条件清 DB api_key, // 唯一密钥副本被覆盖成空 → keyring 也空 → resolve 返空 → provider 报废密钥永久丢失。 // 兜底:发现未迁移态先即时迁移补密钥,迁移成功后再让下方清 DB 明文(收敛到迁移完成态); @@ -181,7 +181,7 @@ pub async fn ai_save_provider( config: None, created_at, updated_at: now_millis(), - // F-260614-04c: enabled/weight 编辑保留原值(负载均衡池可编辑层),新建走默认。 + // c: enabled/weight 编辑保留原值(负载均衡池可编辑层),新建走默认。 enabled, weight, }; @@ -194,7 +194,7 @@ pub async fn ai_save_provider( Ok(id) } -/// F-260614-04c: 轻量更新 provider 池配置(enabled/weight),不改其它字段、不触密钥迁移。 +/// c: 轻量更新 provider 池配置(enabled/weight),不改其它字段、不触密钥迁移。 /// /// 与 `ai_save_provider` 的区别: /// - ai_save_provider 是全量保存(name/base_url/api_key/model...),编辑路径会走 R-PD-1 密钥 @@ -277,7 +277,7 @@ pub async fn ai_delete_provider( provider_id: String, ) -> Result<(), String> { state.ai_providers.delete(&provider_id).await.map_err(err_str)?; - // CR-260615-01:DB 已删则清 keyring 残留密钥(失败仅 warn 不阻断——无 DB 消费方, + // DB 已删则清 keyring 残留密钥(失败仅 warn 不阻断——无 DB 消费方, // 残留 keyring 不可复活;同 id 复用也不会读到旧密钥,因 set 覆盖写) if let Err(e) = delete_provider_secret_async(provider_id.clone()).await { tracing::warn!("[FR-S1] keyring 清理失败 {} (残留但无消费方,不阻断删除): {}", provider_id, e); @@ -310,7 +310,7 @@ fn normalize_provider_type_for_fetch(provider_type: &str) -> String { /// /// 流程: /// 1. DB 取 AiProviderRecord(get_by_id) -/// 2. FR-S1:经 resolve_provider_secret 内存解析真实 api_key(keyring 优先 fallback DB, +/// 2. 经 resolve_provider_secret 内存解析真实 api_key(keyring 优先 fallback DB, /// 绝不进日志/返回值/错误信息) /// 3. provider_type 归一(anthropic→anthropic_compat)+ base_url + api_key 调 /// df_ai::model_fetch::fetch_and_probe → Vec(每个模型名已探测出 4 维度) @@ -329,7 +329,7 @@ pub async fn ai_fetch_models( .map_err(err_str)? .ok_or_else(|| format!("提供商不存在: {}", provider_id))?; - // FR-S1:内存解析密钥,绝不外泄(不入日志/返回值/错误信息) + // 内存解析密钥,绝不外泄(不入日志/返回值/错误信息) let api_key = df_storage::secret::resolve_provider_secret(&provider); let fetch_type = normalize_provider_type_for_fetch(&provider.provider_type); @@ -381,7 +381,7 @@ pub async fn ai_fetch_models( Ok(merged) } -/// 一次性测试连接(P0-1):用**表单当前值**而非 DB 值测试,不写库。 +/// 一次性测试连接用**表单当前值**而非 DB 值测试,不写库。 /// /// 解决:`ai_fetch_models` 从 DB 读 provider,用户改了表单未保存就点「测试连接」时 /// 测的是旧值(base_url/api_key 已改但 DB 未落),给出误导性「成功/失败」。 @@ -416,7 +416,7 @@ pub async fn ai_test_connection( .await .map_err(err_str)? .ok_or_else(|| format!("提供商不存在: {}", pid))?; - // FR-S1:内存解析,绝不外泄(不入日志/返回值/错误信息) + // 内存解析,绝不外泄(不入日志/返回值/错误信息) df_storage::secret::resolve_provider_secret(&provider) }; diff --git a/src-tauri/src/commands/ai/compress.rs b/src-tauri/src/commands/ai/compress.rs index e38649a..38f8ce6 100644 --- a/src-tauri/src/commands/ai/compress.rs +++ b/src-tauri/src/commands/ai/compress.rs @@ -10,7 +10,7 @@ use df_ai::context_helpers::CompressedSummary; use df_ai::provider::{ChatMessage, CompletionRequest, LlmProvider}; -// F-15: 压缩路由 — TaskRequirements(Standard 智力即可,无需工具)。压缩是结构化 +// 压缩路由 — TaskRequirements(Standard 智力即可,无需工具)。压缩是结构化 // 摘要任务,选 Standard 智力保摘要质量;不传 tools(纯文本出,避免 LLM 调工具跑偏)。 // 池空/无匹配兜底 default_model(与 title.rs 一致,行为可预期)。 use df_ai::router::{ @@ -329,7 +329,7 @@ mod tests { assert_eq!(clean_summary(""), ""); } - // ── BUG-2026-07-07/P0-1: LLM 退化重复输出检测 ── + // ── BUG-2026-07-07/LLM 退化重复输出检测 ── #[test] fn clean_summary_rejects_degenerated_repetition() { // 实测样本(9357c27c seq0):同一短语重复 41 次 diff --git a/src-tauri/src/commands/ai/conversation.rs b/src-tauri/src/commands/ai/conversation.rs index 78353ff..8af4e68 100644 --- a/src-tauri/src/commands/ai/conversation.rs +++ b/src-tauri/src/commands/ai/conversation.rs @@ -94,7 +94,7 @@ pub(crate) fn accumulate_tokens(old: Option, add: u32) -> Option { /// 防 read_file 1MB 洞 / list_directory 大体量结果落库后每轮重发累积致 token 暴增 /// (Sprint 19 实测单对话 in=115万 / 消息体 1.6MB)。仅作用于持久化视图,不污染内存真相源。 /// -/// B-260619-02:50KB → 8KB 收紧。治 GLM 1214 messages 非法根因——GLM anthropic 端点对 +/// 50KB → 8KB 收紧。治 GLM 1214 messages 非法根因——GLM anthropic 端点对 /// 单条 tool_result content 限制更严(实测 ~10-20KB 即拒 1214),read_file 默认前 500 行源码 /// 约 11-21KB,旧 50KB 阈值未触发→DB 存全量→reload/下轮重发→GLM 1214 整请求失败。 /// 8KB 对齐 GLM 单条上限留余量;截断后 LLM 仍能据截断片段推理(优于 1214 直接拒收)。 @@ -235,14 +235,14 @@ async fn save_conversation_inner( touch_updated_at: bool, ) { // 取 messages + 懒创建首次落库所需的 provider_id/created_at - // 工具结果(content)超 8KB(B-260619-02:50KB→8KB,治 GLM 1214)时截断头尾各 ~3KB + 中段 + // 工具结果(content)超 8KB50KB→8KB,治 GLM 1214)时截断头尾各 ~3KB + 中段 // 标注(含原字节数),防大体量结果(read_file 1MB 洞 / list_directory 13782 项)落库后每轮重发 // 累积致 token 暴增。仅影响持久化视图,不污染内存真相源(ContextManager)——build_for_request // 仍读全量 messages。 // // per_conv.messages 是唯一真相源(conv_id 来源:本函数入参,与 save 落库的 conv 一致)。 // conv() 惰性建:save 路径 conv 必然已建;极端未建(如启动恢复无 live conv)建空态,save 空消息(幂等)。 - // BUG-2026-07-19: lock 段只 clone 必要读(messages + 元数据),truncate 移出 lock。 + // lock 段只 clone 必要读(messages + 元数据),truncate 移出 lock。 // 原 clone+truncate 全在 lock 内,对话历史长(多轮+大工具结果)时 clone+truncate 持锁可达秒级, // 致 guard.reset/process_tool_calls 等 session lock 竞争超时(aichat 卡死连环:工具卡片不呈现+ // 回答完卡住+下条进队列)。truncate 在 clone 副本上操作,锁外执行不污染 session 真相源,行为等价。 @@ -251,7 +251,7 @@ async fn save_conversation_inner( // 读基线与读 messages 在同一 lock 段(快,无 await),保证两者一致性快照。 // 借用顺序:先 clone session 顶层字段(immutable borrow),再 conv(conv_id) mutable borrow, // 避免同时持有 session 的 mut 和 immut 借用(E0502)。 - // BE1(AC-EFF-L0-1):无变化捷径提前到克隆之前——同一 lock 段内先判「消息数未增且非 dirty」, + // BE1无变化捷径提前到克隆之前——同一 lock 段内先判「消息数未增且非 dirty」, // 无变化则不 clone(每轮无变化 save 省一次全量 clone + 后续 truncate/records 构建)。 let (provider_id, created_at, pinned_goals, msg_len, persisted_count, needs_full_rewrite, mut persist_msgs) = { let mut session = session_arc.lock().await; @@ -313,7 +313,7 @@ async fn save_conversation_inner( } } for m in &mut persist_msgs { - // P0-2:tool result 是结构化 JSON(provider 读工具返回原样), + // tool result 是结构化 JSON(provider 读工具返回原样), // 中段截断会插裸换行+中文省略标记破坏 JSON 结构致 reload/重发 parse FAIL。 // tool result 体量已由 read_file limit 控源,此处跳过 content 截断,仅截 assistant/user 文本。 if !matches!(m.role, df_ai::provider::MessageRole::Tool) { @@ -616,7 +616,7 @@ mod tests { // 中段标注存在 + 标注省略字符数(中段 = 总长 - 头 - 尾 = 9192 - 3072 - 3072 = 3048) assert!(result.contains("已截断")); assert!(result.contains("省略 3048 字符")); - // B-260619-02:截断提示含原字节数(9192 个 'x' = 9192 字节),让 LLM 感知非全文 + // 截断提示含原字节数(9192 个 'x' = 9192 字节),让 LLM 感知非全文 assert!(result.contains("原 9192 字节")); // 结果总长应远小于原长(20k 头 + 20k 尾 + 标注) assert!(result.chars().count() < TRUNCATE_THRESHOLD + 1000); @@ -687,7 +687,7 @@ mod tests { assert_eq!(truncate_parts_for_persist(&[]), None); } - // ---------- P0-2: tool role content 不截断(防 JSON 结构破坏) ---------- + // ---------- tool role content 不截断(防 JSON 结构破坏) ---------- /// 辅助:构造指定 role + content 的 ChatMessage,用 provider 全路径类型 fn make_msg(role: df_ai::provider::MessageRole, content: &str) -> df_ai::provider::ChatMessage { diff --git a/src-tauri/src/commands/ai/generate_image.rs b/src-tauri/src/commands/ai/generate_image.rs index d494df1..29e0ed6 100644 --- a/src-tauri/src/commands/ai/generate_image.rs +++ b/src-tauri/src/commands/ai/generate_image.rs @@ -795,7 +795,7 @@ mod tests { ); } - // ── 真实 SenseNova U1-Fast 图像生成验证(#[ignore],手动跑) ── + // ── 真实 SenseNova ast 图像生成验证(#[ignore],手动跑) ── // // 端到端验证:用真实 db 副本(SenseNova provider 配置)+ OS keyring 解析 api_key + // 真实调 https://token.sensenova.cn/v1/images/generations 生成一张测试图并下载落地。 diff --git a/src-tauri/src/commands/ai/mod.rs b/src-tauri/src/commands/ai/mod.rs index 4a62250..12e76fa 100644 --- a/src-tauri/src/commands/ai/mod.rs +++ b/src-tauri/src/commands/ai/mod.rs @@ -94,7 +94,7 @@ pub use self::augmentation::build_resolver_registry; // 事件载荷类型(放 mod.rs,各子文件经 use super::* 拿到) // ============================================================ -/// AI 错误分类(B-260615-42):供前端按错误源差异化提示(如 auth 引导填 key, +/// AI 错误分类供前端按错误源差异化提示(如 auth 引导填 key, /// network 提示检查连接,provider_config 引导检查 base_url/model)。 /// /// 设计:`Option` 向后兼容——旧 emit 点若难精确分类填 `None`, @@ -178,7 +178,7 @@ pub enum AiChatEvent { }, /// 错误 /// - /// `error_type` 为错误源分类(B-260615-42),`None` 表示未分类(向后兼容旧 emit 点)。 + /// `error_type` 为错误源分类,`None` 表示未分类(向后兼容旧 emit 点)。 AiError { error: String, /// 错误源分类(可选,便于前端差异化提示);旧点/难精确分类填 None @@ -207,13 +207,13 @@ pub enum AiChatEvent { AiAgentRound { round: u32, conversation_id: Option }, /// 流式心跳(静默期报活,前端 watchdog reset,区分「LLM 在跑」与「真断」) AiHeartbeat { conversation_id: Option }, - /// 达最大轮次仍未收敛(F-260616-03:转暂停态询问用户继续/停止) + /// 达最大轮次仍未收敛转暂停态询问用户继续/停止) /// /// 触发时 generating 仍为 true(仿审批等待),前端展示独立操作卡: /// - 点继续 → ai_continue_loop → try_continue_agent_loop 再跑 max_iterations 轮 /// - 点停止 → ai_stop_loop → 走完成流程(save + AiCompleted + generating=false) AiMaxRoundsReached { conversation_id: Option }, - /// 流式调用自动重试提示(F-260616-07:流前失败重试中) + /// 流式调用自动重试提示流前失败重试中) /// /// 每次重试前 emit,前端可在错误气泡内显示「重试 n/m」。 /// attempt 从 1 开始(首次失败后第一次重试=attempt 1),max_attempts = max_retries + 1。 @@ -238,7 +238,7 @@ pub enum AiChatEvent { AiCompressing { conversation_id: Option }, /// F-15 阶段2 手动 LLM 压缩完成:摘要已插入消息首位,前端弹 toast + 刷新视图回填摘要。 /// - /// 治 Task#1(BUG-260624-05):自动压缩路径误用此变体致桌面误弹 toast+每次发送误刷, + /// 治 Task#1自动压缩路径误用此变体致桌面误弹 toast+每次发送误刷, /// 现拆为 AiManualCompressed(手动 IPC,弹 toast) + AiAutoCompressed(loop 自动,静默)。 /// emit 点:ai_chat_compress_context IPC 的 3 处(空会话/无 active/LLM 成功主路径)。 AiManualCompressed { conversation_id: Option, summary: String }, @@ -502,7 +502,7 @@ pub enum SessionState { /// - `Write { dir }`:write_file 在 `dir` 目录下(路径父目录归一化) /// - `Execute { dir }`:run_command 在 `dir` 工作目录下 /// -/// 命中即自动放行,跳过 pending 审批 + 二次确认(AE-05),不写 audit 仍记一条 +/// 命中即自动放行,跳过 pending 审批 + 二次确认,不写 audit 仍记一条 /// `decided_by=auto_trust` 的审计记录留痕。 #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum TrustKey { @@ -518,7 +518,7 @@ pub enum TrustKey { /// (run_command handler 自身仅 validate_path 不限定 workspace,信任目录用 LLM 传入或 /// workspace_root 默认值作为 key 字面量,与 handler 实际 working_dir 对齐) /// -/// FR-S1:不读 api_key / content / command 等敏感字段,只取 path/working_dir 归一化为 dir。 +/// 不读 api_key / content / command 等敏感字段,只取 path/working_dir 归一化为 dir。 pub fn trust_key_for(tool: &str, args: &serde_json::Value) -> Option { match tool { "write_file" => { @@ -1164,10 +1164,10 @@ pub(crate) async fn execute_run_workflow_for_tool( let config = serde_json::Value::Object(serde_json::Map::new()); let name = format!("AI 推进任务 {} → {}", task_id, target_status); - // 转调工作流执行核心(B-260617-01:run_workflow_inner 是 run_workflow 命令的共用核心, + // 转调工作流执行核心run_workflow_inner 是 run_workflow 命令的共用核心, // 同 invoke('run_workflow') IPC 入口的执行逻辑,持 &AppState 非 tauri::State,绕开 // tauri::State 在非命令上下文不可构造的限制)。 - // CR-52: 本路径是 AI 工具经 ai_approve 审批后执行 → triggered_by="ai"(区别命令层 manual)。 + // 本路径是 AI 工具经 ai_approve 审批后执行 → triggered_by="ai"(区别命令层 manual)。 // 返回 execution_id 字符串;失败(模板不存在/DAG 校验失败/DB 写入失败)上抛 Err。 let execution_id = crate::commands::workflow::run_workflow_inner( app, diff --git a/src-tauri/src/commands/ai/prompt.rs b/src-tauri/src/commands/ai/prompt.rs index 4017407..2da59d7 100644 --- a/src-tauri/src/commands/ai/prompt.rs +++ b/src-tauri/src/commands/ai/prompt.rs @@ -269,7 +269,7 @@ fn app_config_query_guidance_section(lang: &str) -> &'static str { } } -// ── BE3(AC-EFF-L0-2): system prompt 会话内缓存 ── +// ── BE3system prompt 会话内缓存 ── // // 背景:同一会话连续轮次(每次 send)都全量重建 system prompt——查活跃 project/task 清单 + // 逐条 format 拼多 KB 字符串,对高频轮次是纯重复成本。 diff --git a/src-tauri/src/commands/ai/provider_pool.rs b/src-tauri/src/commands/ai/provider_pool.rs index 6a81024..25da692 100644 --- a/src-tauri/src/commands/ai/provider_pool.rs +++ b/src-tauri/src/commands/ai/provider_pool.rs @@ -11,7 +11,7 @@ //! `secret.rs::build_provider_for` 同属"消费 AiProviderRecord 的 app 层逻辑",故放 src-tauri。 //! //! ## 与 router 协同(职责切分,不重叠) -//! - **router.select_model_id**:在**单个 provider 的 model_configs 池**中选最优模型(F-01)。 +//! - **router.select_model_id**:在**单个 provider 的 model_configs 池**中选最优模型。 //! - **ProviderPool::select**:在**多个 provider 间**选主 + 列出备用(本模块)。 //! 调用顺序:先 router 选模型 → 再 ProviderPool 选 provider 实例(模型在多 provider 间共享时)。 //! diff --git a/src-tauri/src/commands/ai/stream_recv.rs b/src-tauri/src/commands/ai/stream_recv.rs index 80f2959..85574b9 100644 --- a/src-tauri/src/commands/ai/stream_recv.rs +++ b/src-tauri/src/commands/ai/stream_recv.rs @@ -114,7 +114,7 @@ pub(crate) enum StreamResult { reasoning_content: Option, }, /// Init 失败(provider.stream() Err:建连/鉴权/HTTP non-2xx,或流建立后空文本各类中断)。 - /// UX-260618-15: **不再 emit AiError**——重试可恢复路径的 emit 权交调用方(agentic/mod.rs), + /// **不再 emit AiError**——重试可恢复路径的 emit 权交调用方(agentic/mod.rs), /// 由 agentic 在重试耗尽/Fatal 时统一 emit 最终 AiError(单气泡),避免 N 次重试 push N+1 气泡。 /// `retryable`: 据状态码分类(retry::is_status_retryable 镜像):true=5xx/429/timeout/connect /// 可重试;false=4xx(非429)/鉴权/参数错 Fatal 立即放弃。调用方据此决定是否重试。 @@ -154,7 +154,7 @@ fn emit_ai_chat_event(app_handle: &AppHandle, ev: AiChatEvent) { let _ = app_handle.state::().ai_event_bus.publish_event(ev); } -/// AR-8: flush 合批的 AiTextDelta(双写 emit + event_bus)。空 buffer 不 emit。 +/// flush 合批的 AiTextDelta(双写 emit + event_bus)。空 buffer 不 emit。 fn flush_delta(app_handle: &AppHandle, conv_id: &str, pending_delta: &mut String) { if pending_delta.is_empty() { return; @@ -196,16 +196,16 @@ pub(crate) async fn stream_llm( conv_id: &str, ) -> StreamResult { /// 流式读取空闲超时:超过此时长无任何 chunk 即判定连接已断 - /// BUG-2026-07-17:原 120s 太长,后端挂死时用户等太久。改为 45s,正常流式每 chunk 间隔 + /// 原 120s 太长,后端挂死时用户等太久。改为 45s,正常流式每 chunk 间隔 /// 远不到 45s(即使模型思考间隙),超时即判为连接异常走保文/报错路径 const STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(45); - /// BUG-2026-07-08: 首 chunk 超时(等首字节)。 + /// 首 chunk 超时(等首字节)。 /// 正常 provider 首 token <3s(即使 48 工具定义 + 长上下文),10s 足够宽容; /// 已有数据后中途静默仍用 STREAM_IDLE_TIMEOUT(120s,允许模型思考间隙)。 const FIRST_CHUNK_TIMEOUT: Duration = Duration::from_secs(10); /// 心跳间隔:静默期向前端报「LLM 仍在跑」,reset watchdog const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30); - /// AR-8: 后端 delta 合批窗口。50ms 内的多次 AiTextDelta 合并为一条再 emit, + /// 后端 delta 合批窗口。50ms 内的多次 AiTextDelta 合并为一条再 emit, /// 降低 IPC 事件风暴(前端 rAF 只节流渲染不节流 IPC);合并后前端 currentText /// 逐条累加,最终一致。 const DELTA_FLUSH_INTERVAL: Duration = Duration::from_millis(50); @@ -287,14 +287,14 @@ pub(crate) async fn stream_llm( let mut finished_received = false; let mut stopped = false; let mut final_usage: Option = None; - // BUG-260617-12: DeepSeek thinking 模式推理内容累积(多轮需回传) + // DeepSeek thinking 模式推理内容累积(多轮需回传) let mut reasoning_content_acc: Option = None; - // AR-8: per-conv delta 合批累加器。pending_delta 累积未 flush 的文本, + // per-conv delta 合批累加器。pending_delta 累积未 flush 的文本, // next_flush_at 记录下次 flush 的绝对时刻(首个入 buffer 时置 now+50ms)。 let mut pending_delta = String::new(); let mut next_flush_at: Option = None; - // B-260615-15:heartbeat interval 提至 loop 外复用,避免每轮重建计时器 + // heartbeat interval 提至 loop 外复用,避免每轮重建计时器 // (每轮重建会丢已积累的节拍,且 interval 首次 tick 立即返回的特性会被误用)。 // tokio interval 首 tick 立即返回——此处先丢弃首 tick,让心跳等满首个 30s 静默期才发 // (心跳语义是"静默期仍在跑",循环入口立即报无意义且会与 rx.recv() 抢分支错过首 chunk)。 @@ -310,7 +310,7 @@ pub(crate) async fn stream_llm( let mut first_chunk_done = false; loop { - // B-260615-04:stop 即时打断。 + // stop 即时打断。 if stop_flag.load(std::sync::atomic::Ordering::SeqCst) { stopped = true; break; @@ -348,7 +348,7 @@ pub(crate) async fn stream_llm( }; } - // AR-8: 到点 flush 合批的 delta(单条 AiTextDelta 双写 emit + event_bus)。 + // 到点 flush 合批的 delta(单条 AiTextDelta 双写 emit + event_bus)。 if let Some(deadline) = next_flush_at { if tokio::time::Instant::now() >= deadline { next_flush_at = None; @@ -360,7 +360,7 @@ pub(crate) async fn stream_llm( // 1) chunk_rx.recv():来自专用线程的 LLM chunk (15s 保底 timeout,防 select! 死等) // 2) heartbeat.tick():静默期发 AiHeartbeat reset 前端 watchdog // 3) notify.notified():用户停止即时打断 - // 4) flush 定时器(sleep_until next_flush_at):尾部 delta 不因流静默而延迟(AR-8)。 + // 4) flush 定时器(sleep_until next_flush_at):尾部 delta 不因流静默而延迟。 let flush_deadline = next_flush_at .unwrap_or_else(|| tokio::time::Instant::now() + Duration::from_secs(3600)); let flush_fut = tokio::time::sleep_until(flush_deadline); @@ -412,7 +412,7 @@ pub(crate) async fn stream_llm( idle_deadline = tokio::time::Instant::now() + STREAM_IDLE_TIMEOUT; if !chunk.delta.is_empty() { full_text.push_str(&chunk.delta); - // AR-8: 合批入 buffer,首个入 buffer 时置 flush 时刻(到点统一 emit 单条)。 + // 合批入 buffer,首个入 buffer 时置 flush 时刻(到点统一 emit 单条)。 pending_delta.push_str(&chunk.delta); if next_flush_at.is_none() { next_flush_at = Some(tokio::time::Instant::now() + DELTA_FLUSH_INTERVAL); @@ -514,20 +514,20 @@ pub(crate) async fn stream_llm( } } } - // 2) 心跳:静默期 30s 发 AiHeartbeat,前端 watchdog reset(B-260615-02) + // 2) 心跳:静默期 30s 发 AiHeartbeat,前端 watchdog reset _ = heartbeat.tick() => { emit_ai_chat_event(app_handle, AiChatEvent::AiHeartbeat { conversation_id: Some(conv_id.to_string()), }); } - // 3) 即时停止唤醒(B-260615-14) + // 3) 即时停止唤醒 _ = notify.notified() => { if stop_flag.load(std::sync::atomic::Ordering::SeqCst) { stopped = true; break; } } - // 4) AR-8: delta 合批到点 flush(流静默/工具执行等待期也能按时发出尾部文本) + // 4) delta 合批到点 flush(流静默/工具执行等待期也能按时发出尾部文本) _ = &mut flush_fut => { if next_flush_at.is_some() { next_flush_at = None; @@ -589,7 +589,7 @@ pub(crate) async fn stream_llm( /// - timeout / connect → retryable=true(瞬态,可重试) /// - unknown / 其他 → retryable=true(保守可重试,避免误判 Fatal 错杀) /// -/// CR-30-1: 抽公共分类函数(决策 F-260616-07 a1 "复用 retry.rs 错误分类逻辑或镜像"), +/// 抽公共分类函数(决策 F-260616-07 a1 "复用 retry.rs 错误分类逻辑或镜像"), /// 避免 stream_recv 与 agentic 各自重写状态码解析。 fn classify_status_or_class(status_or_class: &str) -> bool { use df_ai::retry::is_status_retryable; @@ -612,7 +612,7 @@ fn classify_status_or_class(status_or_class: &str) -> bool { i += 1; } // 无三位数字码:按文本分类(timeout/connect/transport/decode 可重试,其余 unknown 保守不重试) - // BUG-260617-01:Anthropic 中文参数错误(如 `[1214][messages 参数非法]`)无 HTTP 数字前缀, + // Anthropic 中文参数错误(如 `[1214][messages 参数非法]`)无 HTTP 数字前缀, // 走此文本分支,原 || true 致 retryable=true 空耗 4 次重试浪费 token。 // 删 || true 对齐 retry::is_status_retryable 未知码不可重试语义;timeout/connect 瞬态仍可重试。 // @@ -694,7 +694,7 @@ fn classify_error_frame_retryable(msg: &str, status_or_class: &str) -> bool { "api-key", "api_key", "unauthorized", - // 中文签名(BUG-260617-01:Anthropic 中文参数错误) + // 中文签名Anthropic 中文参数错误) "鉴权", "认证失败", "未授权", @@ -746,7 +746,7 @@ mod tests { assert!(classify_status_or_class("connect")); } - /// unknown → 保守 retryable=false(BUG-260617-01:防 Anthropic 中文参数错误等 Fatal 空耗重试) + /// unknown → 保守 retryable=false防 Anthropic 中文参数错误等 Fatal 空耗重试) #[test] fn classify_unknown_not_retryable() { assert!(!classify_status_or_class("unknown")); @@ -941,7 +941,7 @@ mod tests { /// 状态码出现在更长数字串里也不误匹配(边界判断:前后必须非数字) #[test] fn diag_does_not_match_status_inside_longer_digits() { - // R-P2-6:边界判断后,白名单码嵌在长数字串内不再误命中(原窗口切片会从 14012 抠出 401) + // 边界判断后,白名单码嵌在长数字串内不再误命中(原窗口切片会从 14012 抠出 401) let e = anyhow::anyhow!("port 14012 used"); assert_eq!(extract_error_diag(&e).0, "unknown"); // 纯长数字串(无白名单码)同样不命中 @@ -957,7 +957,7 @@ mod tests { assert_eq!(s, "[anthropic-compat] AI 调用失败(HTTP 401): Unauthorized"); } - /// R-P2-6:状态码紧贴中文字符(多字节 UTF-8)时仍能正确抠出 + /// 状态码紧贴中文字符(多字节 UTF-8)时仍能正确抠出 /// (原按字节窗口切片在中文边界脆弱,改为 char 迭代后安全) #[test] fn diag_extracts_code_adjacent_to_multibyte_chars() { diff --git a/src-tauri/src/commands/ai/title.rs b/src-tauri/src/commands/ai/title.rs index 478f218..0de26ae 100644 --- a/src-tauri/src/commands/ai/title.rs +++ b/src-tauri/src/commands/ai/title.rs @@ -8,7 +8,7 @@ use tokio::sync::Mutex; use df_ai::provider::{ChatMessage, CompletionRequest, LlmProvider, MessageRole}; // F-01 阶段5: 标题生成路由 — TaskRequirements(无需工具)。 // 标题是短文本摘要任务;池空/无匹配兜底 default_model(行为不变)。 -// 注:路由已解耦(B-260618-03),原 Lite/Low 智力成本约束已删除,纯 weight 选模型。 +// 注:路由已解耦,原 Lite/Low 智力成本约束已删除,纯 weight 选模型。 use df_ai::router::{ is_likely_image_model, select_model_id, Modality, TaskRequirements, }; @@ -121,7 +121,7 @@ pub(crate) async fn ensure_conversation_title( ); return; } - // B-260617-17: LLM 调用包 20s 超时,防信号量/网络挂起致后台 task 永久堆积; + // LLM 调用包 20s 超时,防信号量/网络挂起致后台 task 永久堆积; // 超时或返回 None 均保留上方已落的 extract 兜底,不覆盖。 let title = match tokio::time::timeout( std::time::Duration::from_secs(20), diff --git a/src-tauri/src/commands/ai/tool_registry.rs b/src-tauri/src/commands/ai/tool_registry.rs index 2542e80..af264dc 100644 --- a/src-tauri/src/commands/ai/tool_registry.rs +++ b/src-tauri/src/commands/ai/tool_registry.rs @@ -33,7 +33,7 @@ use crate::state::AllowedDirs; pub(crate) const MAX_LIST_RESULTS: usize = 50; /// run_command 默认超时(秒)。LLM 可在 args timeout_secs 覆盖此默认值。 -/// 提取为常量便于在超时标注处引用同一来源(F-260616-04)。 +/// 提取为常量便于在超时标注处引用同一来源。 /// /// tool_registry 拆分:run_command handler 迁声明式(tools/file.rs),复用本常量保单真相源 /// (避免字面量 60 漂移),故 pub(crate) 暴露给 tools 子模块。 @@ -48,7 +48,7 @@ pub(crate) const MAX_RUN_COMMAND_TIMEOUT_SECS: u64 = 600; /// AE-2025-03(路径 B):audit.rs 挂起审批前预读旧文件复用此函数生成 diff, /// 供前端审批卡即时预览(write_file 审批不再只看裸 content)。故 pub(crate)。 /// -/// BUG-260617-07: LCS DP 表 O(n*m) 内存,两文件均>1000 行时 DP 表可达数百 MB +/// LCS DP 表 O(n*m) 内存,两文件均>1000 行时 DP 表可达数百 MB /// (5000×5000×8B≈200MB)。虽然 1MB 字节上限能挡住多数情况,但短行高密度的源码 /// (大量空行/单字符行)仍可能突破。改:两文件均>1000 行时跳过 LCS,退化为 /// 「删旧全量 + 增新全量」朴素行对比 + 截断提示。朴素对比 O(n+m) 内存,安全。 @@ -118,7 +118,7 @@ pub(crate) fn generate_diff(old: &str, new: &str) -> String { /// 验证文件路径:禁止访问系统敏感目录 + 防路径遍历绕过 /// -/// 安全要点(BUG-260617-03): +/// 安全要点 /// 1. **先 URL 解码**再检查——防 `%2e%2e` / `%2f` 等 URL 编码绕过 `..` 检测。 /// LLM / 外部输入可能传编码串,若直接做字符串 `..` 检查会被 `%2e%2e` 骗过, /// 解码后再走分段归一化校验。 @@ -151,7 +151,7 @@ pub(crate) fn validate_path(path: &str) -> anyhow::Result<()> { if crate::state::is_in_system_blacklist(&PathBuf::from(&normalized)) { anyhow::bail!("禁止访问敏感系统目录"); } - // F-260620: AppData 不再硬拦(原 `lower.contains("\\appdata\\")` 一刀切致用户授权后仍拒)。 + // AppData 不再硬拦(原 `lower.contains("\\appdata\\")` 一刀切致用户授权后仍拒)。 // AppData 走白名单流程(is_authorized/check_path_authorization):默认不在白名单 → 弹 DirAuthDialog // → 用户显式授权(once/always)→ 放行。真正敏感凭据(.ssh/.aws/.gnupg)由 is_in_system_blacklist // 硬拦(授权也不放,防凭据泄漏),AppData 内应用数据(DBeaver Scripts/项目配置等)归用户自治—— @@ -180,7 +180,7 @@ pub(crate) fn truncate_output(s: &str, max: usize) -> (String, bool) { } // ============================================================ -// patch_file 三模式共用底层(F-260617-01) +// patch_file 三模式共用底层 // // 三模式互斥(old_text 精确匹配 / replace_lines 行号区间 / anchor 锚点), // 后两者均归约为「按行号区间 [start, end](1-based 含首尾)替换为 new_text」, @@ -407,7 +407,7 @@ pub(crate) fn resolve_workspace_path_with_allowed( resolve_workspace_path_impl(path, allowed) } -/// AE-2025-04:mod.rs trust_key_for 计算 write_file 的 TrustKey 目录粒度时复用本函数 +/// mod.rs trust_key_for 计算 write_file 的 TrustKey 目录粒度时复用本函数 /// 归一化路径(去 .. / symlink 逃逸)。pub(crate) wrapper 转调私有 impl,保持原私有函数 /// 可见性边界(不暴露给 crate 外,但允许同 crate ai 模块 mod.rs 调用)。 /// @@ -631,7 +631,7 @@ fn register_local_proxy_tools( /// 数据层 AI 工具注册(25 个持 db 的 CRUD/状态机/工作流/知识图谱工具)——从 build_ai_tool_registry 抽出。 /// /// 工具闭包捕获 `db: &Arc` Arc 重建 Repo(列表/创建/更新/删除/状态推进/工作流/任务关联)。 -/// 例外:run_workflow handler 防御返回 Err(CR-52),不持 db 不 clone,真正执行经 +/// 例外:run_workflow handler 防御返回 Err,不持 db 不 clone,真正执行经 /// ai_approve → run_workflow_inner 另走完整 State 路径。 /// /// 知识图谱 Phase 1(2026-06-26):新增 register_task_graph_tools 子函数(6 工具:task_link CRUD @@ -641,7 +641,7 @@ fn register_local_proxy_tools( /// 知识图谱 Phase 3(2026-06-27):register_task_graph_tools 增 add_project_service(Medium)/ /// list_project_services(Low 只读) 项目基础设施配置,data 层 25→27。 /// -/// SMELL-P0-2:抽自原 build_ai_tool_registry 1091 行单函数(数据+文件混合)。 +/// 抽自原 build_ai_tool_registry 1091 行单函数(数据+文件混合)。 /// 18 个 register 调用【原样移入】,零行为变更,仅机械搬运。 /// /// SMELL-P0-2 续拆(2026-06-19):18 工具按业务表细分为 5 子函数: @@ -761,7 +761,7 @@ fn register_trash_tools(registry: &mut AiToolRegistry, db: &Arc) { } /// 文件系统类 AI 工具注册(命令执行/读/列/写/改/元/追加/删/移/搜)——不持 db,纯文件系统操作。 -/// 从 build_ai_tool_registry 抽出(SMELL-P0-2:1091行单函数拆分,build 1090→~390 行)。 +/// 从 build_ai_tool_registry 抽出1091行单函数拆分,build 1090→~390 行)。 /// /// F-260619-03 Phase A: 各文件工具闭包捕获 `allowed_dirs: Arc>`, /// handler 执行时 read lock 取白名单快照,resolve_workspace_path_with_allowed 校验动态白名单。 @@ -852,11 +852,11 @@ pub(crate) fn list_dir_recursive<'a>( continue; } // 临时文件(.bak/.tmp-write 等)也不返回:write_file 原子写过程的副产物, - // 崩溃/中断会留孤儿文件,泄漏进 list_directory 噪化 AI 上下文(CR-260615-03)。 + // 崩溃/中断会留孤儿文件,泄漏进 list_directory 噪化 AI 上下文。 if skip_noise && is_noise_file(&name) { continue; } - // FR-S8:用 file_type() 不跟随 symlink——entry.metadata() 会解析符号链接目标, + // 用 file_type() 不跟随 symlink——entry.metadata() 会解析符号链接目标, // 经 workspace 内 symlink 即可泄露外部目标的 size/类型,且递归会进入 symlink 指向的 workspace 外目录 let file_type = entry.file_type().await?; let is_symlink = file_type.is_symlink(); @@ -929,7 +929,7 @@ pub(crate) fn tool_display_hint(name: &str) -> Option<&'static str> { "create_idea" => Some("捕获灵感"), "update_task" => Some("修改任务"), // advance_task 不在此轻量映射:让 build_approval_reason 走 display_hint_for_tool - // 模板(含 target_status 详情),否则轻量命中"推进任务状态"丢失目标态信息(UX-260618-14) + // 模板(含 target_status 详情),否则轻量命中"推进任务状态"丢失目标态信息 "run_workflow" => Some("触发工作流"), "delete_task" => Some("删除任务"), "update_idea" => Some("修改灵感"), @@ -948,7 +948,7 @@ pub(crate) fn is_noise_dir(name: &str) -> bool { const NOISE_DIRS: &[&str] = &[ ".git", "node_modules", "target", "dist", "build", ".next", ".cache", "__pycache__", ".venv", "venv", ".idea", - ".trash", // delete_file 软删除回收站(F-260615-16),不污染 list_directory + ".trash", // delete_file 软删除回收站,不污染 list_directory ]; NOISE_DIRS.contains(&name) } @@ -964,7 +964,7 @@ pub(crate) fn is_noise_file(name: &str) -> bool { } // ============================================================ -// grep 工具底层(F-260621) +// grep 工具底层 // // FileGrepHit 单文件命中聚合:line_matches 按行号升序。output_mode 分派时: // - content: 展平 line_matches 为 matches[{file,line,content,context?}] @@ -1101,7 +1101,7 @@ pub(crate) fn grep_recursive<'a>( } continue; } - // 文件处理(BUG-260625-01:提取为 grep_one_file,grep handler 单文件路径复用同一逻辑) + // 文件处理提取为 grep_one_file,grep handler 单文件路径复用同一逻辑) grep_one_file(&path, &file_name, re, glob, output_mode, context_lines, max_results, out, total, truncated).await?; } Ok(()) @@ -1109,7 +1109,7 @@ pub(crate) fn grep_recursive<'a>( } /// 单文件 grep:读 + 二进制检测 + 行匹配,收集命中到 out。 -/// 提取自 grep_recursive 循环体(BUG-260625-01),供 grep handler 单文件路径复用—— +/// 提取自 grep_recursive 循环体,供 grep handler 单文件路径复用—— /// grep 命令行原生支持单文件,LLM 常传文件路径而非目录,旧实现直接 read_dir 文件路径报 os error 267。 /// files_with_matches:文件级命中(content/count 不收集行详情);content/count:行级命中,达 max_results 截断。 pub(crate) async fn grep_one_file( @@ -1317,7 +1317,7 @@ mod tests { let registry = build_ai_tool_registry(&db, &allowed_dirs, PathBuf::from(""), make_test_get_app_config_ctx(&db), make_test_local_proxy_ctx()); // 总量基线:41(27 data + 13 file + 1 http)。拆分前后必须一致。 - // F-260621: file 层 10→11(新增 grep 跨文件内容搜索工具)。 + // file 层 10→11(新增 grep 跨文件内容搜索工具)。 // L1 环境感知(设计 §2.1): file 层 11→12(新增 detect_environment 环境探测工具)。 // AST 代码智能(7c2e3b2): file 层 12→13(新增 read_symbol 符号解析,治 read_file 全文回灌 prompt 爆)。 // 知识图谱 Phase 1(2026-06-26): data 层 18→24(新增 register_task_graph_tools 6 工具: @@ -1332,7 +1332,7 @@ mod tests { // register_git_tools 3 个) // Git 写工具(2026-06-29): data 层 31→34(新增 git_commit/git_branch/git_merge, // register_git_tools 3→6) - // BUG-2026-07-07: 修正 baseline 计数(原写死 41 与实际 48 不符,HEAD 上即失败)。 + // 修正 baseline 计数(原写死 41 与实际 48 不符,HEAD 上即失败)。 // 48 = 34 data + 13 file + 1 http。data 34 含 6 知识图谱 + 2 基础设施 + 1 module + 6 git。 // update_idea/delete_idea(2026-08-01): data 层 34→36(补 AI 改/删灵感专用工具, // idea_repo.update_field / soft_delete 早已具备,工具层此前缺失致 AI 误用 update_task)。 @@ -1780,7 +1780,7 @@ mod tests { } // ============================================================ - // patch_file 三模式共用底层测试(F-260617-01) + // patch_file 三模式共用底层测试 // 纯函数 apply_line_range / resolve_anchor_to_lines,不依赖 fs/async/锁。 // handler 闭包内的三模式分派/互斥校验逻辑由参数解析+这两个函数组合而成, // 故覆盖纯函数即覆盖 replace_lines/anchor 两模式核心;old_text 模式逻辑 @@ -1942,7 +1942,7 @@ mod tests { } // ============================================================ - // grep 工具测试(F-260621) + // grep 工具测试 // // compile_glob_to_regex:纯函数,无 fs 依赖。 // grep_recursive:递归遍历临时目录树,覆盖基本匹配/正则/大小写/glob/噪音跳过/截断。 diff --git a/src-tauri/src/commands/ai/tools/file.rs b/src-tauri/src/commands/ai/tools/file.rs index 2725e2a..4519b55 100644 --- a/src-tauri/src/commands/ai/tools/file.rs +++ b/src-tauri/src/commands/ai/tools/file.rs @@ -81,7 +81,7 @@ pub fn register( &snap, )?; let path = resolved.to_str().ok_or_else(|| anyhow::anyhow!("路径含非法字符"))?; - // 单次 File::open 取 metadata+read 消除 TOCTOU(FR-S2:原 metadata+read 两步 syscall 间 symlink 替换破 1MB 限) + // 单次 File::open 取 metadata+read 消除 TOCTOU原 metadata+read 两步 syscall 间 symlink 替换破 1MB 限) use tokio::fs::File; use tokio::io::AsyncReadExt; let mut file = File::open(path).await @@ -143,7 +143,7 @@ pub fn register( })); } // 默认分页模式: limit 硬上限 2000 行(防 LLM 传超大 limit 读全文件,1MB 限下仍可能数万行) - // BUG-260617-11: 无 offset 时旧实现 content.clone() 全量返回大文件, + // 无 offset 时旧实现 content.clone() 全量返回大文件, // 虽 1MB 字节上限挡住极端情况,但万行级源码全量进 LLM context 仍易撑爆。 // 改:无 offset 默认返回前 500 行 + has_more 提示翻页(对齐 read 工具常规用法)。 let line_count = content.lines().count(); @@ -157,7 +157,7 @@ pub fn register( } else { // 无 offset: 尊重 LLM 传入的 limit(对齐有 offset 分支语义), // 未传 limit 默认 500 行(大文件翻页友好,避免一次灌入全量)。 - // BUG-260623-01: 旧实现固定 take(500) 忽略 limit, + // 旧实现固定 take(500) 忽略 limit, // 实测 LLM 传 limit=15 仍返回 430 行(撑爆 prompt + 触发 truncate 破坏 JSON)。 const DEFAULT_PREVIEW_LINES: usize = 500; let limit = args["limit"].as_u64() @@ -319,7 +319,7 @@ pub fn register( let path = resolved.to_str().ok_or_else(|| anyhow::anyhow!("路径含非法字符"))?; let recursive = args["recursive"].as_bool().unwrap_or(false); let skip_noise = args["skip_noise_dirs"].as_bool().unwrap_or(true); - // BUG-260617-09: max_depth 由 LLM 参数控制,无上限时虽 entries 上限(1000) + // max_depth 由 LLM 参数控制,无上限时虽 entries 上限(1000) // 隐式约束,但深递归仍可能大量 fs IO / 撑爆上下文。clamp 到合理范围 1-10。 let max_depth = args["max_depth"].as_u64().unwrap_or(3).clamp(1, 10) as usize; let mut entries = Vec::new(); @@ -348,7 +348,7 @@ pub fn register( )?; let path = resolved.to_str().ok_or_else(|| anyhow::anyhow!("路径含非法字符"))?; let raw = args["content"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 content 参数"))?; - // F-260615-11: encoding 参数支持 base64,写二进制(图片/PDF/Excel 等) + // encoding 参数支持 base64,写二进制(图片/PDF/Excel 等) // 默认 utf-8:走原 &str 字节写入;base64:解码为 Vec 写字节 let encoding = args.get("encoding").and_then(|v| v.as_str()).unwrap_or("utf-8"); let bytes: Vec = if encoding.eq_ignore_ascii_case("base64") { @@ -360,7 +360,7 @@ pub fn register( } else { anyhow::bail!("不支持的 encoding: {}(仅 utf-8 / base64)", encoding); }; - // 写入大小上限(FR-S2:原无限制,LLM 经审批可写超大文件撑爆磁盘/对话历史) + // 写入大小上限原无限制,LLM 经审批可写超大文件撑爆磁盘/对话历史) if bytes.len() > 1_048_576 { anyhow::bail!("写入内容超过 1MB 限制 ({} 字节)", bytes.len()); } @@ -378,8 +378,8 @@ pub fn register( Err(_) => None, // 不存在(新建) }; if let Some(parent) = target.parent() { - // FR-S8:parent 必须在授权目录内(防 path=授权根时 parent 越界 create_dir_all)。 - // BUG-260623-01:原用 workspace_root()(编译期写死 devflow 源码目录)→ 用户授权的其他项目 + // parent 必须在授权目录内(防 path=授权根时 parent 越界 create_dir_all)。 + // 原用 workspace_root()(编译期写死 devflow 源码目录)→ 用户授权的其他项目 // 目录(ai-news 等)parent 不 starts_with 它 → 误拒授权目录内写。改用 allowed_dirs 白名单。 if !snap.is_authorized(parent) { anyhow::bail!("禁止在项目目录之外创建目录"); @@ -397,7 +397,7 @@ pub fn register( let _ = tokio::fs::remove_file(&tmp).await; return Err(anyhow::anyhow!("原子替换失败: {}", e)); } - // R-P2-2:rename 成功后清理 .bak(原子写已完成,.bak 不再需要); + // rename 成功后清理 .bak(原子写已完成,.bak 不再需要); // rename 失败分支不删 .bak——它是回退依据(失败分支已 return,不会走到这里)。 // 仅当 old_size>0(曾备份过)才清理;忽略清理失败(非阻断,最多留个孤儿 .bak 文件) if old_size.map(|s| s > 0).unwrap_or(false) { @@ -464,7 +464,7 @@ pub fn register( let new_text = args["new_text"].as_str() .ok_or_else(|| anyhow::anyhow!("缺少 new_text 参数"))?; - // F-260617-01: 三模式互斥校验(old_text / replace_lines / anchor) + // 三模式互斥校验(old_text / replace_lines / anchor) // 三选一:统计传入的模式参数数,>1 报错,0 报错(缺定位方式) let has_old_text = args.get("old_text").map(|v| !v.is_null()).unwrap_or(false); let has_replace_lines = args.get("replace_lines").map(|v| !v.is_null()).unwrap_or(false); @@ -491,7 +491,7 @@ pub fn register( anyhow::bail!("文件超过 1MB 限制 ({} 字节)", file_meta.len()); } - // TD-260621-03:读改写整体锁内防 lost update。 + // 读改写整体锁内防 lost update。 // 原实现读+校验+new_content 计算在无锁段,仅写序列持 FILE_LOCKS → 两并发 patch 同文件: // A/B 各自读 v1 算 new_content(锁外)→ A 持锁写 v2 释放 → B 持锁用基于 v1 的 new_content 覆盖 A。 // 改:读+校验+算+写 全程持 _patch_guard,串行化 patch(全局锁,单用户桌面够用,见 FILE_LOCKS 注释)。 @@ -528,7 +528,7 @@ pub fn register( } } - // F-260617-01: 三模式分派计算 new_content + match_count + warning + // 三模式分派计算 new_content + match_count + warning // old_text 模式:精确匹配(CAS)+ 多匹配警告(仅替换第 1 处) // replace_lines 模式:行号区间 splice(越界 Err) // anchor 模式:子串锚点定位 → 行号区间 splice(找不到/start 在 end 后 Err) @@ -548,7 +548,7 @@ pub fn register( })); } // L2: old_text 精确匹配(CAS 语义) - // 失败时附相近片段提示(AC-5):把文件里与 old_text 相似的几行列给 LLM, + // 失败时附相近片段提示把文件里与 old_text 相似的几行列给 LLM, // 供其对照真实缩进/空格修正锚点,避免靠猜反复重试。完全无关(无相似行)则 // 退回原提示,不误导。 if !content.contains(old_text) { @@ -656,7 +656,7 @@ pub fn register( let modified = metadata.modified() .ok().and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) .map(|d| d.as_millis() as i64); - // is_binary: 流式读前 8KB 检测 \x00 (BUG-260617-02) + // is_binary: 流式读前 8KB 检测 \x00 // 旧实现 tokio::fs::read(path) 把整个文件读进内存再切片前 8192, // >2MB 文件触发 OOM(注释">2MB 跳过"只作用于 lines, is_binary 无防护)。 // 改:File::open + BufReader + .take(8192) 只读前 N 字节做二进制检测, @@ -701,7 +701,7 @@ pub fn register( let path = resolved.to_str().ok_or_else(|| anyhow::anyhow!("路径含非法字符"))?; let content = args["content"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 content 参数"))?; if let Some(parent) = std::path::Path::new(path).parent() { - // BUG-260623-01:用 allowed_dirs 校验(非 workspace_root 编译期写死),授权目录内 parent 放行 + // 用 allowed_dirs 校验(非 workspace_root 编译期写死),授权目录内 parent 放行 if !snap.is_authorized(parent) { anyhow::bail!("禁止在项目目录之外创建目录"); } @@ -779,7 +779,7 @@ pub fn register( let backup_name = format!("{}-{}", new_id(), file_name); let backup_path = trash_dir.join(&backup_name); let backup_path_str = backup_path.to_string_lossy().to_string(); - // 软删除跨盘降级(F-260621):同盘 rename 原子;跨盘(Windows EXDEV os error 17, + // 软删除跨盘降级同盘 rename 原子;跨盘(Windows EXDEV os error 17, // 如 C盘授权路径 → data_dir/.trash)rename 失败,降级 copy + remove(非原子, // 失败回滚删 backup 保源完整)。 // 阶段4:跨盘降级抽统一 helper rename_or_cross_volume_copy(对齐 rename_file 跨卷处理), @@ -846,7 +846,7 @@ pub fn register( // 目标父目录不存在则创建(对齐 write_file L643/append_file L851,跨目录移动到不存在父目录否则 rename 失败) let to_target = std::path::Path::new(to_path); if let Some(parent) = to_target.parent() { - // BUG-260623-01:用 allowed_dirs 校验(非 workspace_root 编译期写死),授权目录内 parent 放行 + // 用 allowed_dirs 校验(非 workspace_root 编译期写死),授权目录内 parent 放行 if !snap.is_authorized(parent) { anyhow::bail!("禁止在项目目录之外创建目录"); } @@ -959,7 +959,7 @@ pub fn register( let mut matches_out: Vec = Vec::new(); let mut total: usize = 0; let mut truncated = false; - // BUG-260625-01: path 可能是单文件(grep 命令行原生支持单文件,LLM 常传文件路径)。 + // path 可能是单文件(grep 命令行原生支持单文件,LLM 常传文件路径)。 // 旧实现直接 grep_recursive(root) → 内部 read_dir → 文件路径报 os error 267「目录名称无效」。 // 改:文件走 grep_one_file(单文件匹配),目录走 grep_recursive(递归)。 let root_meta = tokio::fs::metadata(root).await @@ -1103,7 +1103,7 @@ pub fn register( let mut out = serde_json::json!({ "path": path, "pattern": pattern, "results": page_results, "total": total, "has_more": has_more }); - // AC-5:total=0 时空结果引导(治 LLM 搜不到就放弃/盲目重试)。 + // total=0 时空结果引导(治 LLM 搜不到就放弃/盲目重试)。 // 注:search_files 已大小写不敏感(内部 pattern.to_lowercase + 包含匹配),文案勿写"换大小写"。 if total == 0 { out["hint"] = serde_json::json!("未找到匹配文件。可尝试: ① 放宽关键词(更短/字符更少); ② 用 grep 工具做跨文件内容正则搜索(pattern 支持正则, -i 大小写不敏感)。注: search_files 为文件名大小写不敏感包含匹配"); @@ -1156,7 +1156,7 @@ pub fn register( timeout_secs: Some(timeout_secs), shell_type: Default::default(), }; - // F-260616-04:超时标注——execute 超时返 Err("命令执行超时: N秒")。 + // 超时标注——execute 超时返 Err("命令执行超时: N秒")。 // 原行为:该 Err 经 ? 上抛 → 人工审批路径(commands.rs ai_approve L256)把 e.to_string() // 包成 tool_result 回传 LLM → LLM 误判命令失败而非超时 → 盲目重试同命令 → // 新 tool_call_id → 重新 insert pending → 重新审批,「再过一会又提示 Run Command」循环。 @@ -1391,13 +1391,13 @@ fn similar_line_fragments(content: &str, needle: &str, max: usize) -> Vec Vec { if needle.is_empty() { @@ -1445,7 +1445,7 @@ fn similar_file_names(dir: &std::path::Path, needle: &str, max: usize) -> Vec anyhow::Error { diff --git a/src-tauri/src/commands/ai/tools/git.rs b/src-tauri/src/commands/ai/tools/git.rs index e58ffa8..05e9e3d 100644 --- a/src-tauri/src/commands/ai/tools/git.rs +++ b/src-tauri/src/commands/ai/tools/git.rs @@ -34,7 +34,7 @@ use df_storage::db::Database; /// 只读工具(status/diff/log)直接调本函数拿退出码做差异语义:git diff 退 1 = 有差异是 /// **合法成功**、git log 退 128 = 非 git 仓是失败;单纯 bool(exit==0)无法区分。 /// -/// BUG-2026-07-18: 原实现 spawn_blocking 内裸 std::process::Command::output() 无 timeout +/// 原实现 spawn_blocking 内裸 std::process::Command::output() 无 timeout /// (注释谎称"10s 超时")。git 在 OneDrive/网盘/挂载盘/lfs/大仓库场景会卡数十秒到无限, /// spawn_blocking 线程永不返回 → 累计耗尽 tokio blocking 池 → 间接卡死单线程 runtime /// (与 env_snapshot::probe_version 同型病根)。AI 的 git_status/log/diff 工具在会话内高频触发。 @@ -42,7 +42,7 @@ use df_storage::db::Database; /// 改用 tokio::process + tokio::time::timeout(10s) + kill_on_drop:超时 drop 时 child 进程 /// 被 kill,不泄漏线程/进程,对齐 shell.rs execute 同源封装。 /// -/// BUG-2026-08-02: 原签名返 String,失败/超时统一吞成空串,使 create/switch/commit 三个写操作 +/// 原签名返 String,失败/超时统一吞成空串,使 create/switch/commit 三个写操作 /// 恒返成功假象(commit 失败 HEAD 不动,log -1 仍返上次 hash → committed:true)。 /// 改返 (String, bool),上层据 success 判定真实成败。 async fn exec_git_exit(working_dir: &str, args: &[&str]) -> (String, Option) { diff --git a/src-tauri/src/commands/ai/tools/project.rs b/src-tauri/src/commands/ai/tools/project.rs index 93b6f73..2718e31 100644 --- a/src-tauri/src/commands/ai/tools/project.rs +++ b/src-tauri/src/commands/ai/tools/project.rs @@ -111,7 +111,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc) { let stack_opt = args.get("stack").and_then(|v| v.as_str()).filter(|s| !s.is_empty()); let repo = df_storage::crud::ProjectRepo::new(&db); let stack = bind_dir_to_project(&repo, id, path, stack_opt).await?; - // F-260619-03: 项目绑定目录加 AI 白名单由 reload_allowed_dirs 自动读 projects.bind_directory + // 项目绑定目录加 AI 白名单由 reload_allowed_dirs 自动读 projects.bind_directory // 统一处理(优雅:绑定 = 自动授权,reload 时合并)。bind handler 不直接写 allowed_dirs // (register_project_tools 无 allowed_dirs 参数,且 reload 集中更易维护)。 // 绑定后触发 reload:通过 app_handle emit 事件让 state reload,或下次工具调用前 reload 兜底。 diff --git a/src-tauri/src/commands/ai/tools/task.rs b/src-tauri/src/commands/ai/tools/task.rs index ba63e25..bf41a13 100644 --- a/src-tauri/src/commands/ai/tools/task.rs +++ b/src-tauri/src/commands/ai/tools/task.rs @@ -150,7 +150,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc) { // 透传 affected:0 行(id 不存在 / 已软删)→ updated=false + title 空, // 让 AI 知道 id 不对而非假成功(对齐 update_idea)。 let updated = repo.update_field(id, field, value).await?; - // UX-260619-04: 返回 title 供前端卡片友好展示(对齐 advance_task 返 TaskRecord, + // 返回 title 供前端卡片友好展示(对齐 advance_task 返 TaskRecord, // 防裸 UUID id 辨识度低)。update_field 返 bool 不含 record,单独 get_by_id 取 title。 let title = repo.get_by_id(id).await?.map(|t| t.title).unwrap_or_default(); Ok(serde_json::json!({ "id": id, "title": title, "field": field, "updated": updated })) @@ -158,7 +158,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc) { ); // F-03 收口(子项1):advance_task AI 工具 — 推进任务状态的唯一合法路径。 - // D-260616-03:推进链业务逻辑(状态机校验 + 原子 CAS + review_rounds 累加)落 + // 推进链业务逻辑(状态机校验 + 原子 CAS + review_rounds 累加)落 // df-nodes::task_advance_node::advance_task_atomic,本 handler 直接调它(与 IPC // commands::task::advance_task 同源,thin 转发),无需 AppHandle/State/EventBus。 // 风险 Medium:status 变更属状态机推进(非破坏),但改变任务流转语义需人工知情。 @@ -203,7 +203,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc) { } ); - // FR-D6:delete_task 对齐 commands::task::delete_task(D-260616-02 软删除) + // delete_task 对齐 commands::task::delete_task(D-260616-02 软删除) // tasks 表 V14 加 deleted_at,delete_task 改调 soft_delete 移入回收站(对标 delete_project) declare_tool!( registry, diff --git a/src-tauri/src/commands/ci_status.rs b/src-tauri/src/commands/ci_status.rs index 6a81ca3..3ce5a57 100644 --- a/src-tauri/src/commands/ci_status.rs +++ b/src-tauri/src/commands/ci_status.rs @@ -53,7 +53,7 @@ fn parse_repo_url(repo_url: &str) -> Option<(String, String, String)> { return None; } let host = url.host_str()?; - // BUG-2026-07-07: port_or_known_default() 对 https 返 Some(443)、http 返 Some(80), + // port_or_known_default() 对 https 返 Some(443)、http 返 Some(80), // 致 authority 总是带默认端口(:443/:80),base URL 形如 "https://host:443" 而非规范 // "https://host"。下游 Gitea API 调用虽大多容忍,但测试断言与可读性受影响。 // 修复:仅当端口非 scheme 默认端口时才拼入 authority(显式非默认端口才保留)。 diff --git a/src-tauri/src/commands/idea.rs b/src-tauri/src/commands/idea.rs index 5eeacc9..7b25420 100644 --- a/src-tauri/src/commands/idea.rs +++ b/src-tauri/src/commands/idea.rs @@ -34,7 +34,7 @@ fn default_priority() -> i32 { /// 列出灵感。 /// -/// **双路径向后兼容**(F-260621-02): +/// **双路径向后兼容** /// - 旧调用方仅传 `status`(`ideaApi.list(status)`)→ 转 IdeaQuery 仅带 status,走 /// `list_by_query`(白名单 status 列 WHERE),与原 `query("status", s)` 等价。 /// - 新调用方传 `query`(`ideaApi.list(query)`)→ 多条件(status/keyword/order_by/limit/offset)。 @@ -73,7 +73,7 @@ pub async fn create_idea( state: State<'_, AppState>, input: CreateIdeaInput, ) -> Result { - // IDEA-FIX-03: priority 值域校验 ∈ 0..=3 (对标 tasks B-260615-15)。 + // priority 值域校验 ∈ 0..=3 (对标 tasks B-260615-15)。 // priority_from_i32 对越界值兜底归并(>=3→Critical),但 IPC 入口应显式拒非法值, // 防 LLM/前端传 99 等被静默吞为 Critical。 if !(0..=3).contains(&input.priority) { @@ -121,7 +121,7 @@ pub async fn update_idea( field: String, value: String, ) -> Result { - // BE-CMD-4:status 值合法性校验(防任意值进库)+ 拒绝经 update_field 直达 promoted + // status 值合法性校验(防任意值进库)+ 拒绝经 update_field 直达 promoted // (半立项:绕过 promote_idea 不建项目不写 promoted_to,须走立项流程)。 if field == "status" { if IdeaStatus::from_db_str(value.trim()).is_none() { @@ -142,7 +142,7 @@ pub async fn update_idea( serde_json::from_str::(&value) .map_err(|e| format!("{field} 不是合法 JSON: {e}"))?; } - // LW-6(BE-CMD-5):update_field_active 过滤软删(deleted_at IS NULL),回收站灵感不可改字段。 + // update_field_active 过滤软删(deleted_at IS NULL),回收站灵感不可改字段。 let updated = state .ideas .update_field_active(&id, &field, &value) @@ -230,7 +230,7 @@ pub async fn promote_idea( .await .map_err(err_str)?; - // LW-8(BE-CMD-7):CAS 回写灵感(status=promoted + promoted_to,WHERE id AND promoted_to IS NULL)。 + // LW-8CAS 回写灵感(status=promoted + promoted_to,WHERE id AND promoted_to IS NULL)。 // 双击/并发两次 promote 都读到 promoted_to=None → 各自建项目;本方法原子「立项认领」, // 仅首个 affected=1 成功,第二个 affected=0 → 判定「已立项」并补偿软删刚建项目(回滚)。 // 替代原 update_full(无条件覆盖):并发下两个项目都保留、灵感只指向一个,留悬空项目。 @@ -386,7 +386,7 @@ async fn evaluate_one( engine: &df_ideas::adversarial::AdversarialEngine, ) -> Result { let id = record.id.clone(); - // LW-7(BE-CMD-6):终态灵感不可再评估(防无条件覆盖 pending_review 打回终态)。 + // LW-7终态灵感不可再评估(防无条件覆盖 pending_review 打回终态)。 // promoted(已立项)/archived(已归档)是终态,评估会把 status 覆盖回 pending_review, // 破坏「已立项/已归档不可回退」语义。软删灵感由 evaluate_idea/batch 的存在性检查已过滤。 if matches!(record.status, IdeaStatus::Promoted | IdeaStatus::Archived) { @@ -545,7 +545,7 @@ fn record_to_idea(record: &IdeaRecord) -> Idea { }, None => Vec::new(), }; - // IDEA-FIX-05: 读真实 related_ids(原硬编码 Vec::new() 丢关联上下文)。解析同 tags 模式。 + // 读真实 related_ids(原硬编码 Vec::new() 丢关联上下文)。解析同 tags 模式。 let related_ids: Vec = match record.related_ids.as_deref() { Some(r) => match serde_json::from_str::>(r) { Ok(v) => v, @@ -560,7 +560,7 @@ fn record_to_idea(record: &IdeaRecord) -> Idea { id: record.id.clone(), title: record.title.clone(), description: record.description.clone(), - // IDEA-FIX-05: 读真实 status(原硬编码 Draft 丢真实状态,评估上下文完整性) + // 读真实 status(原硬编码 Draft 丢真实状态,评估上下文完整性) status: status_from_str(record.status.as_str()), priority: priority_from_i32(record.priority), scores: None, @@ -587,7 +587,7 @@ fn priority_from_i32(p: i32) -> Priority { } } -/// IDEA-FIX-05: idea status 字符串(DB snake_case)→ IdeaStatus 枚举。 +/// idea status 字符串(DB snake_case)→ IdeaStatus 枚举。 /// record_to_idea 读真实 status 用(原硬编码 Draft 丢真实状态)。未知值 fallback Draft(同原行为)。 fn status_from_str(s: &str) -> df_types::types::IdeaStatus { use df_types::types::IdeaStatus; diff --git a/src-tauri/src/commands/project.rs b/src-tauri/src/commands/project.rs index a366742..bc99b3b 100644 --- a/src-tauri/src/commands/project.rs +++ b/src-tauri/src/commands/project.rs @@ -8,7 +8,7 @@ use tauri::State; use df_ai::provider::{ChatMessage, CompletionRequest}; // F-01 阶段5: 项目扫描描述路由 — TaskRequirements(采样含图时追加 Vision, // 当前采样(ProjectSample)未含图像,故仅 Text)。池空/无匹配兜底 default_model。 -// 注:路由已解耦(B-260618-03),原 Standard 智力约束已删除,纯 weight 选模型。 +// 注:路由已解耦,原 Standard 智力约束已删除,纯 weight 选模型。 use df_ai::router::{ select_model_id, Modality, TaskRequirements, }; @@ -78,7 +78,7 @@ pub struct CreateProjectInput { /// 列出未删除项目(过滤回收站)。 /// -/// F-260621-02:吃可选 query(关键词/排序/分页),向后兼容——不传(或 None)走 list_active +/// 吃可选 query(关键词/排序/分页),向后兼容——不传(或 None)走 list_active /// 全量(deleted_at IS NULL + created_at DESC),零破坏;传 query 走 list_by_query 动态 WHERE。 /// /// 问题3(项目最近活跃排序):默认无 query 路径改走 list_active_with_activity,返回 @@ -228,7 +228,7 @@ async fn create_with_binding( tracing::warn!("创建项目时自动建工程失败(非阻断): {}", e); } } - // F-260620: 绑定目录变更后立即 reload 白名单(reload_allowed_dirs 读 projects.path 合并 persistent), + // 绑定目录变更后立即 reload 白名单(reload_allowed_dirs 读 projects.path 合并 persistent), // 否则新绑定目录需重启/改 Settings 才生效 → AI 访问误弹窗(已绑定重复弹窗主因,agent3 问题5) if record.path.is_some() { state.reload_allowed_dirs().await; @@ -338,7 +338,7 @@ pub async fn get_project( /// 更新项目单个字段(字段名走 df-storage 白名单校验) /// -/// B-260801-01(P0-1):0 行(id 不存在 / 已软删)→ 返 Err 而非假成功。原实现无视 +/// B-260801-010 行(id 不存在 / 已软删)→ 返 Err 而非假成功。原实现无视 /// update_field 返回值恒返 Ok(true),前端误以为成功但库无变更(UI/DB 不一致)。 /// /// 返回值保持 bool(成功恒 true):前端 api 包装 `Promise` 契约不变零破坏, @@ -351,7 +351,7 @@ pub async fn update_project( field: String, value: String, ) -> Result { - // BE-CMD-3:白名单剔除 id/created_at 已下沉 storage(allowed_columns_for,update_field 直拒, + // 白名单剔除 id/created_at 已下沉 storage(allowed_columns_for,update_field 直拒, // 防改主键/伪造创建时间致子表悬空),此处补剩余字段的语义校验: // - status:值合法性(ProjectStatus::from_db_str,防任意值进库静默归 planning) // - stack:JSON 合法性(防脏 JSON 落库) @@ -396,7 +396,7 @@ pub async fn update_project( state.reload_allowed_dirs().await; return Ok(true); } - // LW-6(BE-CMD-5):update_field_active 过滤软删(deleted_at IS NULL),回收站项目不可改字段。 + // update_field_active 过滤软删(deleted_at IS NULL),回收站项目不可改字段。 let updated = state .projects .update_field_active(&id, &field, &value) @@ -405,7 +405,7 @@ pub async fn update_project( if !updated { return Err(format!("项目 ID {id} 不存在或已删除")); } - // F-260620: path 字段变更后立即 reload 白名单(新绑定/重绑目录即时生效,防 AI 误弹窗) + // path 字段变更后立即 reload 白名单(新绑定/重绑目录即时生效,防 AI 误弹窗) if field == "path" { state.reload_allowed_dirs().await; } @@ -448,7 +448,7 @@ pub async fn purge_project(state: State<'_, AppState>, id: String) -> Result, input: CreateTaskInput, ) -> Result { - // ── BE-CMD-1:空 title trim 拒空 + priority 值域校验(对齐前端 PRIORITY_LABELS 与 + // ── 空 title trim 拒空 + priority 值域校验(对齐前端 PRIORITY_LABELS 与 // update_task 校验,拦截空白标题 / 99 等脏数据静默落库)。 ── let title = input.title.trim().to_string(); if title.is_empty() { @@ -321,7 +321,7 @@ pub async fn create_task( base_branch: None, review_rounds: 0, output_json: None, - // F-260619-01:空字符串视为不关联(与 AI 工具层一致) + // 空字符串视为不关联(与 AI 工具层一致) idea_id: input.idea_id.filter(|s| !s.is_empty()), // 多工程:可选关联到具体工程(module) module_id: input.module_id.filter(|s| !s.is_empty()), @@ -348,7 +348,7 @@ pub async fn create_task( Some(record.status.as_str()), ) .await; - // LW-9(BE-CMD-8):建子任务后触发父聚合重算(容器模型,父 status 由子任务聚合)。 + // LW-9建子任务后触发父聚合重算(容器模型,父 status 由子任务聚合)。 // 聚合失败仅 warn 不阻断(对齐 advance_task 宽容语义)。 if let Some(pid) = &record.parent_id { if let Err(e) = recompute_parent_status(&state.tasks, pid).await { @@ -365,7 +365,7 @@ pub async fn create_task( /// 更新任务单个字段(字段名走 df-storage 白名单校验;status 值走枚举校验)。 /// -/// B-260801-01(P0-1):0 行(id 不存在 / 已软删)→ 返 Err 而非 false,杜绝「假成功」 +/// B-260801-010 行(id 不存在 / 已软删)→ 返 Err 而非 false,杜绝「假成功」 /// (前端 updateTask 仅 await 不读 bool,但 store.runWithCatch 把 Err 转 toast,用户能感知; /// 若返 false 则 store 静默忽略,本地状态前端手动改但库无变更,造成 UI 与 DB 不一致)。 /// @@ -439,7 +439,7 @@ pub async fn update_task( } // 空字符串 = None(解除父),合法放行 } - // LW-4(BE-CMD-1):queue 字段校验(白名单 + queue/status 一致性,读当前 status)。 + // queue 字段校验(白名单 + queue/status 一致性,读当前 status)。 // update_task 只改单字段不改 status,故用严格一致性校验(与 move_task_queue 自动联动不同): // 设置不兼容的 queue 直接拒绝,调用方需走 move_task_queue 自动联动调整 status。 if field == "queue" { @@ -453,13 +453,13 @@ pub async fn update_task( .ok_or_else(|| format!("任务 ID {id} 不存在或已删除"))?; assert_queue_status_consistent(q, current.status.as_str())?; } - // B-260801-01(P0-1):update_field 返 affected>0;false = id 不存在或已软删(0 行)。 + // update_field 返 affected>0;false = id 不存在或已软删(0 行)。 // 不可静默返 false——前端 store.runWithCatch 把 Err 转 toast,而 false 会被忽略致假成功。 // // 任务字段更新是高频「项目活跃」信号(改 title/priority/assignee 等),埋点 task_updated // 推动项目最近活跃排序反映真实业务(问题3)。best-effort 不阻断。 // 读当前 project_id(一次轻量读):update_field 返 bool 不带 project_id,无法直接埋点。 - // LW-6(BE-CMD-5):update_field_active 过滤软删(deleted_at IS NULL),回收站任务不可改字段。 + // update_field_active 过滤软删(deleted_at IS NULL),回收站任务不可改字段。 let current = state .tasks .get_by_id(&id) @@ -473,7 +473,7 @@ pub async fn update_task( if !updated { return Err(format!("任务 ID {id} 不存在或已删除")); } - // LW-9(BE-CMD-8):改 parent_id 后重算父聚合——旧父(任务已离开,计数减少)与新父(任务挂上)。 + // LW-9改 parent_id 后重算父聚合——旧父(任务已离开,计数减少)与新父(任务挂上)。 // 从 current 读旧 parent_id(pre-update),value 是新父值。聚合失败仅 warn 不阻断。 if field == "parent_id" { if let Some(old_pid) = current.as_ref().and_then(|c| c.parent_id.clone()) { @@ -590,7 +590,7 @@ pub async fn restore_task(state: State<'_, AppState>, id: String) -> Result` — 成功返更新后的任务;失败 Err(状态机拒绝/CAS 冲突/ @@ -794,7 +794,7 @@ pub async fn move_task_queue( .await; } - // LW-9(BE-CMD-8):跨池移动后子任务 status 可能联动变化(一致性联动)→ 触发其父聚合重算。 + // LW-9跨池移动后子任务 status 可能联动变化(一致性联动)→ 触发其父聚合重算。 // 仅当被移动任务是子任务;父任务自身 move 不重算自身聚合(容器 status 由子任务推进时重算)。 // 聚合失败仅 warn 不阻断。 if let Some(pid) = &updated.parent_id { diff --git a/src-tauri/src/commands/workflow.rs b/src-tauri/src/commands/workflow.rs index 47da4dc..1041f34 100644 --- a/src-tauri/src/commands/workflow.rs +++ b/src-tauri/src/commands/workflow.rs @@ -102,9 +102,9 @@ pub async fn run_workflow( task_id: Option, target_status: Option, ) -> Result { - // 瘦转发到 run_workflow_inner(B-260617-01:抽取共用核心供 ai_approve 经 + // 瘦转发到 run_workflow_inner抽取共用核心供 ai_approve 经 // execute_run_workflow_for_tool 直接调,无需构造 tauri::State)。 - // CR-52: 命令层(前端 invoke run_workflow)是人工手动触发 → triggered_by="manual"。 + // 命令层(前端 invoke run_workflow)是人工手动触发 → triggered_by="manual"。 run_workflow_inner(&app, &state, name, dag, config, task_id, target_status, "manual").await } @@ -117,7 +117,7 @@ pub async fn run_workflow( /// `triggered_by` 落入 WorkflowRecord.triggered_by,区分触发来源: /// - 命令层 run_workflow(前端 invoke,人工手动)传 "manual" /// - execute_run_workflow_for_tool(AI 工具经 ai_approve 审批后执行)传 "ai" -/// CR-52:原硬编码 "manual" 致 AI 路径误标,改由调用方透传。 +/// 原硬编码 "manual" 致 AI 路径误标,改由调用方透传。 pub async fn run_workflow_inner( app: &AppHandle, state: &AppState, @@ -148,7 +148,7 @@ pub async fn run_workflow_inner( // 1. 先构建运行时 DAG,校验失败直接返回,不落库 let runtime_dag = state.registry.build_dag(&dag).map_err(err_str)?; - // 2. P2-I: 反查 project_id(task_id 有值时),不阻塞工作流 + // 2. 反查 project_id(task_id 有值时),不阻塞工作流 let project_id = match &task_id { Some(tid) => TaskRepo::new(&state.db) .get_by_id(tid) @@ -167,7 +167,7 @@ pub async fn run_workflow_inner( name, dag_json, status: "running".to_string(), - // CR-52: triggered_by 由调用方透传(命令层 run_workflow="manual", + // triggered_by 由调用方透传(命令层 run_workflow="manual", // execute_run_workflow_for_tool="ai"),不再硬编码 "manual" 致 AI 路径误标。 triggered_by: Some(triggered_by.to_string()), project_id, @@ -185,7 +185,7 @@ pub async fn run_workflow_inner( let mut rx = state.event_bus.subscribe(); let forward_app = app.clone(); let forward_exec_id = execution_id.clone(); - // B-260615-35: forward 任务内需查 DB 终态兜底 Lagged 丢终态事件,故 move 进 db 句柄 + // forward 任务内需查 DB 终态兜底 Lagged 丢终态事件,故 move 进 db 句柄 let forward_db = state.db.clone(); tauri::async_runtime::spawn(async move { // Lagged 累计次数(用于"累计达阈值"触发兜底查询) @@ -195,7 +195,7 @@ pub async fn run_workflow_inner( loop { match rx.recv().await { Ok(event) => { - // B-03b-R10 ③(波17 治本): 完成判定加 execution_id 匹配。 + // b-R10 ③(波17 治本): 完成判定加 execution_id 匹配。 // AppState.event_bus 是全局单例,并发工作流各自订阅同一总线; // 此前 matches! 只看变体不看 exec_id,任一工作流的 WorkflowCompleted/Failed // 都会触发本 forward 循环 break,过早断他人链(并发串扰根因)。 @@ -222,7 +222,7 @@ pub async fn run_workflow_inner( break; } } - // R-PD-13: Lagged 表示消费过慢,broadcast 滑动窗口丢 n 条最旧事件。 + // Lagged 表示消费过慢,broadcast 滑动窗口丢 n 条最旧事件。 // broadcast 不暴露被丢事件的具体类型,无法在此精确判断是否为关键终态事件。 // 风险:若关键终态事件(WorkflowCompleted/WorkflowFailed)被丢,finished 判定不触发, // 循环不会 break,前端永久收不到工作流结束信号。 @@ -303,7 +303,7 @@ pub async fn run_workflow_inner( .lock() .await .insert(exec_id.clone(), executor.state_machine()); - // P0-A: 把 task_id 注入 config,使 AiSelfReviewNode(ctx.config.get("task_id")) 与 + // 把 task_id 注入 config,使 AiSelfReviewNode(ctx.config.get("task_id")) 与 // AiNode parse_params 兜底(ctx.config.get("task_id") 读 task 生成默认 prompt) 可读。 // config 是 serde_json::Value,仅 object 时 insert 安全;task_id None 跳过(向后兼容)。 // executor.run 之前的 config 是 move 进 run 的,此处 mutate 后整体 move,零额外开销。 @@ -478,7 +478,7 @@ pub async fn get_workflow_execution( /// 发送人工审批响应 /// -/// F-260615-01: 支持 single/multiple。 +/// 支持 single/multiple。 /// - single: decision 单值,decisions 为空(向后兼容); /// - multiple: decisions 数组(≥1 项),decision 留空。 /// IPC 早校验规则与 HumanNode 下游兜底一致,避免「IPC 成功+工作流失败」割裂。 @@ -489,12 +489,12 @@ pub async fn approve_human_approval( execution_id: ExecutionId, node_id: String, decision: String, - // F-260615-01: 多选结果数组,缺省空数组(单选调用方不传) + // 多选结果数组,缺省空数组(单选调用方不传) decisions: Option>, comment: Option, - // R-PD-5: options 由前端从收到的 HumanApprovalRequest 事件透传(IPC 无法访问节点 config)。 + // options 由前端从收到的 HumanApprovalRequest 事件透传(IPC 无法访问节点 config)。 options: Vec, - // F-260615-01: 选择类型,缺省 single + // 选择类型,缺省 single select_type: Option, ) -> Result<(), String> { let _ = &app; // 原签名含 app 参数(未使用),保留避免 invoke_handler 注册签名变化 diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 69bbae1..83d536e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -70,13 +70,13 @@ pub fn run() { // 初始化全局状态(打开数据库 + 执行迁移),传入运行期确定的 data_dir let app_state = tauri::async_runtime::block_on(AppState::init(&db_path, data_dir.clone()))?; - // FR-S1:启动一次性迁移 DB 明文 api_key → OS keyring(失败保留明文下次重试,非阻断) + // 启动一次性迁移 DB 明文 api_key → OS keyring(失败保留明文下次重试,非阻断) if let Err(e) = tauri::async_runtime::block_on( commands::ai::secret::migrate_secrets_to_keyring(&app_state.ai_providers), ) { tracing::warn!("[FR-S1] 启动密钥迁移失败(非阻断): {}", e); } - // B-260616-01: L0 握手 — 提前 clone ai_session(Arc),因 manage() 会 move app_state + // L0 握手 — 提前 clone ai_session(Arc),因 manage() 会 move app_state let session_for_handshake = app_state.ai_session.clone(); app.manage(app_state); @@ -100,7 +100,7 @@ pub fn run() { tracing::error!("[tray] 托盘创建失败(非阻断): {}", e); } - // B-260616-01: L0 握手 — 监听前端就绪事件,清除 HMR/刷新导致的残留 generating 状态 + // L0 握手 — 监听前端就绪事件,清除 HMR/刷新导致的残留 generating 状态 // 任务5: 3 秒防抖 —— 前端 HMR/快速连击会连发 ai-client-ready(实测 <1s 内多次), // 每次都走完整 握手(spawn + 锁 session + emit)造成事务事并行冲突 + emit 风暴。 // 记录上次处理时间,3s 内重复事件跳过(只取首次,使能状态复位一次即可)。 @@ -422,7 +422,7 @@ pub fn run() { commands::ai::ai_chat_force_send, commands::ai::ai_chat_stop, commands::ai::ai_approve, - // F-260616-03:达 max_iterations 暂停态续/停(消费 AiMaxRoundsReached,前端操作卡留 batch45) + // 达 max_iterations 暂停态续/停(消费 AiMaxRoundsReached,前端操作卡留 batch45) commands::ai::ai_continue_loop, commands::ai::ai_stop_loop, commands::ai::ai_pending_tool_calls, @@ -435,12 +435,12 @@ pub fn run() { commands::ai::ai_save_provider, commands::ai::ai_set_provider, commands::ai::ai_delete_provider, - // F-260614-04c: 负载均衡池可编辑层(enabled/weight 轻量更新 + caps 重建) + // c: 负载均衡池可编辑层(enabled/weight 轻量更新 + caps 重建) commands::ai::ai_update_provider_pool, // F-01 阶段5:测试连接拉取模型列表 + 单模型探测 commands::ai::ai_fetch_models, commands::ai::ai_probe_model, - // P0-1:用表单当前值一次性测试连接(不写库,新建/编辑未保存均可测) + // 用表单当前值一次性测试连接(不写库,新建/编辑未保存均可测) commands::ai::ai_test_connection, // AI 对话管理 commands::ai::ai_conversation_create, @@ -464,7 +464,7 @@ pub fn run() { commands::ai::ai_set_allowed_dirs, // F-260619-03 Phase B: 路径授权弹窗决策(消费 AiDirAuthRequired 挂起) commands::ai::ai_authorize_dir, - // 审批历史面板(AE-2025-08:查 ai_tool_executions 表,敏感字段截断) + // 审批历史面板查 ai_tool_executions 表,敏感字段截断) commands::ai::audit::record::list_tool_executions, // AC-5 按工具失败率统计(诊断 IPC,聚合 ai_tool_executions 覆盖全量/时间范围) commands::ai::audit::record::tool_failure_stats, diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index 8268c7f..a8007dd 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -316,7 +316,7 @@ impl AppState { // 启动恢复:重启前卡 pending 的工具审批(内存 pending_approvals 已丢)从审计表重建, // 使重启后待审批不丢。前端经 ai_pending_tool_calls + switchConversation 恢复 toolCard 态。 crate::commands::ai::restore_pending_approvals(&state).await; - // F-260614-04c: 启动一次性初始化 per-provider caps 表。 + // c: 启动一次性初始化 per-provider caps 表。 // 根据 DB enabled providers 建 HashMap,让 agentic loop 的 // try_acquire_for_provider 从 NotConfigured(无限流)切换到 Acquired/Exhausted(按配置限流)。 // disabled / weight=0 的 provider 不入表(其被 provider_pool::select 过滤出候选,不会被 acquire)。 @@ -352,10 +352,10 @@ impl AppState { Ok(state) } - /// F-260614-04c: 从 DB enabled providers 重建 per_provider caps 并 set_provider_caps。 + /// c: 从 DB enabled providers 重建 per_provider caps 并 set_provider_caps。 /// /// 启动 + provider 变更(ai_update_provider_pool / 删除 / 新增)后调用。caps 策略见 - /// AppState::init 注释(本轮每 provider cap = global_cap,F-04d 配差异化上限时仅改此)。 + /// AppState::init 注释(本轮每 provider cap = global_cap,d 配差异化上限时仅改此)。 pub async fn reload_provider_caps(&self) { let providers = match self.ai_providers.list_all().await { Ok(ps) => ps, @@ -425,7 +425,7 @@ impl AppState { /// 每条路径尝试 canonicalize 规范化(防大小写/分隔符差异绕过);canonicalize 失败 /// (目录不存在)回退原字面量 trim(写入后再校验场景:先授权目录路径,目录暂不存在)。 pub async fn reload_allowed_dirs(&self) { - // F-260619-03: 白名单 = Settings KV allowed_dirs(用户配)+ projects.bind_directory(项目绑定目录自动授权)。 + // 白名单 = Settings KV allowed_dirs(用户配)+ projects.bind_directory(项目绑定目录自动授权)。 // 项目绑定目录 = AI 天然可访问(用户已主动绑定),不应重复弹窗。reload 时自动合并入 persistent。 let kv_dirs: Vec = match self.settings.get(AllowedDirs::SETTINGS_KEY).await { Ok(Some(v)) => serde_json::from_str(&v).unwrap_or_default(), @@ -592,7 +592,7 @@ impl AppState { /// 这里注册 df-nodes 提供的真实 HumanNode / AiNode。 /// /// "script" 节点不注册:ScriptNode 走 cmd /C | sh -c 执行 config.command 原始串, -/// 前端可构造任意 DagDef 触达无审批 shell(R-PD-2)。DevFlow 工作流当前为纯演示 +/// 前端可构造任意 DagDef 触达无审批 shell。DevFlow 工作流当前为纯演示 /// 功能(前端唯一构造点 ProjectDetail.vue demoDag 三步 echo),无真实构建/部署脚本 /// 需求。需要脚本执行能力时新建独立 BuildNode(白名单 + 项目目录锚定 + 复用 AI 工具 /// RiskLevel 审批链),而非回头启用 ScriptNode + 黑名单。 @@ -621,7 +621,7 @@ fn build_registry(db: Arc) -> NodeRegistry { // 推进链阶段 2 工作流联动入口 — DAG 内触发 advance_task。 // 持有 Arc 在此构造时注入(NodeRegistry::register 工厂闭包 move 捕获 db, // Arc clone 廉价),Node::execute 从 NodeContext.config 读 task_id/target_status。 - // D-260616-03: 推进链/状态机/闸门走 df-nodes Node trait,非复活 df-task。 + // 推进链/状态机/闸门走 df-nodes Node trait,非复活 df-task。 registry.register("task_advance", move |_config| { Box::new(df_nodes::task_advance_node::TaskAdvanceNode::new(db.clone())) }); @@ -662,11 +662,11 @@ mod tests { use super::*; // ============================================================ - // F-260620: strip_verbatim 比对侧收口 + 黑名单增强测试 + // strip_verbatim 比对侧收口 + 黑名单增强测试 // 三方审查(安全/UX/跨端)交叉印证:strip_verbatim 仅写入侧调用,比对侧遗漏致误弹窗。 // ============================================================ - /// F-260620: strip_verbatim 三前缀(\\?\ / \\.\ / \??\)统一去除 + /// strip_verbatim 三前缀(\\?\ / \\.\ / \??\)统一去除 #[test] fn test_strip_verbatim_three_prefixes() { assert_eq!(strip_verbatim(PathBuf::from(r"\\?\E:\foo")), PathBuf::from("E:\\foo")); diff --git a/src-tauri/src/state/allowed_dirs.rs b/src-tauri/src/state/allowed_dirs.rs index 3021090..e7f9433 100644 --- a/src-tauri/src/state/allowed_dirs.rs +++ b/src-tauri/src/state/allowed_dirs.rs @@ -168,7 +168,7 @@ pub fn check_path_authorization( // Phase C: 黑名单优先独立判定(is_authorized 内也判,此处先判便于 NeedsAuthorization // 不误把黑名单路径推到弹窗 — 黑名单路径直接硬拒不让用户"授权")。 // - // BUG-2026-07-07: 黑名单检查必须在路径规范化(is_absolute / root.join)之前对**原始路径** + // 黑名单检查必须在路径规范化(is_absolute / root.join)之前对**原始路径** // 也判一次。原因:Windows 上 "/home/user/.ssh/config" 这类 Unix 风格路径 is_absolute() 返 false // (Windows 绝对路径需盘符或 UNC),会被当相对路径走 first_persistent_dir 分支;若 persistent 为空, // 旧逻辑直接 return NeedsAuthorization,**跳过黑名单检查** → .ssh/.aws 等敏感路径在 Windows 上绕过黑名单。 @@ -443,7 +443,7 @@ mod tests { } } - /// BUG-2026-07-07: Windows 上 Unix 风格 .ssh 路径必须被黑名单拒(不能绕过)。 + /// Windows 上 Unix 风格 .ssh 路径必须被黑名单拒(不能绕过)。 /// /// 背景:Windows 上 `/home/user/.ssh/config` 的 is_absolute() 返 false(无盘符), /// 旧逻辑在 persistent 为空时直接返 NeedsAuthorization,**跳过黑名单检查**, @@ -476,11 +476,11 @@ mod tests { // ============================================================ - // F-260620: strip_verbatim 比对侧收口 + 黑名单增强测试 + // strip_verbatim 比对侧收口 + 黑名单增强测试 // 三方审查(安全/UX/跨端)交叉印证:strip_verbatim 仅写入侧调用,比对侧遗漏致误弹窗。 // ============================================================ - /// F-260620: is_authorized 比对侧 strip_verbatim — candidate 带 \\?\ 前缀也命中白名单 + /// is_authorized 比对侧 strip_verbatim — candidate 带 \\?\ 前缀也命中白名单 /// (handler canonicalize 后路径 vs persistent 词法路径,形态不一致曾致误弹窗)。 /// persistent 用动态 workspace_root_path(非硬编码开发机路径),candidate 拼 verbatim 前缀。 #[test] @@ -497,7 +497,7 @@ mod tests { assert!(allowed.is_authorized(&dev), "设备命名空间前缀路径应命中白名单"); } - /// F-260620: strip_verbatim 三前缀(\\?\ / \\.\ / \??\)统一去除 + /// strip_verbatim 三前缀(\\?\ / \\.\ / \??\)统一去除 #[test] fn test_strip_verbatim_three_prefixes() { assert_eq!(strip_verbatim(PathBuf::from(r"\\?\E:\foo")), PathBuf::from("E:\\foo")); @@ -507,7 +507,7 @@ mod tests { assert_eq!(strip_verbatim(PathBuf::from("E:\\foo")), PathBuf::from("E:\\foo")); } - /// F-260620: 黑名单增强 — Windows 根本身 / ProgramData / 用户凭据目录(.ssh/.aws/.gnupg) + /// 黑名单增强 — Windows 根本身 / ProgramData / 用户凭据目录(.ssh/.aws/.gnupg) #[test] fn test_blacklist_windows_root_programdata_creds() { // C:\Windows 根本身(不再限定 system32 子目录 — 防 win.ini/hosts 等) diff --git a/src-tauri/src/state/llm_concurrency.rs b/src-tauri/src/state/llm_concurrency.rs index 160153e..4b8960d 100644 --- a/src-tauri/src/state/llm_concurrency.rs +++ b/src-tauri/src/state/llm_concurrency.rs @@ -32,7 +32,7 @@ use tokio::sync::{Mutex, Semaphore}; /// 新请求 lock 后克隆到最新 Arc、自动走新限制。旧 Semaphore 随最后 permit 释放而 drop。 /// per_conv HashMap 内每条 Arc 不需运行时改 permits(对话内并发上限 2 固定),故无重建需求。 /// -/// ## F-260614-04: per-provider 层(可选) +/// ## per-provider 层(可选) /// `per_provider` 为 HashMap>。调用方(agentic loop)经 /// `try_acquire_for_provider(pid)` 取额外 permit,防单 provider 被打满(限流 429)。 /// **单 provider 场景**:若未调 `set_provider_caps`,HashMap 为空, @@ -75,7 +75,7 @@ pub struct LlmConcurrency { /// 热改后**已建对话**的旧 Semaphore 不变(permits 构造时固定),**新建对话**用新值; /// 为使热改立即全量生效,set_per_conv 同时清空 HashMap 强制重建(软收敛:旧 permit 随 Drop 释放)。 per_conv_permits: Arc, - /// F-260614-04: per-provider 信号量表。空 = 无 per-provider 限流(单 provider 路径零变化)。 + /// per-provider 信号量表。空 = 无 per-provider 限流(单 provider 路径零变化)。 /// Arc>:运行时增删 provider 配置时替换/插入,acquire 时 clone Arc。 per_provider: Arc>>>, /// Phase3 预留(批2-B): per-sub_flow 信号量表。key = sub_flow_id(约定 "{conv_id}::{sub_id}")。 @@ -306,7 +306,7 @@ impl LlmConcurrency { } } - /// F-260614-04: 批量设置 per-provider 并发上限(替换整表)。 + /// 批量设置 per-provider 并发上限(替换整表)。 /// /// 调用方(Settings 配置热改 / 启动初始化)传入 `{ provider_id: permits }` map, /// 替换整张 per_provider 表(软收敛:已持 permit 不回收)。全局容量约束 min(sum, global_cap) diff --git a/src/App.vue b/src/App.vue index f81e475..de4f995 100644 --- a/src/App.vue +++ b/src/App.vue @@ -101,7 +101,7 @@ watch(() => projectStore.error, (err) => { errorTimer = setTimeout(() => { errorMsg.value = '' }, 3500) }) -// ── 人工审批对话框(全局,P0-C:工作流推进到 HumanNode 审批时,任意页面都应能响应)── +// ── 人工审批对话框(全局,工作流推进到 HumanNode 审批时,任意页面都应能响应)── // 原 ProjectDetail 局部渲染 → TaskDetail 无 ApprovalDialog 致 store.pendingApproval 被设但 // 用户在 TaskDetail 页无法操作 → HumanNode 超时失败。提到 App.vue 全局化:任何路由响应审批。 // (系统级浮窗 useApprovalPopup 是窗口失焦时的提示;此处是聚焦时弹的对话框,二者职责互补) @@ -174,7 +174,7 @@ onMounted(async () => { await migrateLegacyLocalStorage() // 3. 缓存已就绪,初始化主题/locale initTheme() - // F-260618-23: 根 onMounted 恢复 Agentic 轮次/重试到后端(AtomicUsize 纯内存,重启回默认)。 + // 根 onMounted 恢复 Agentic 轮次/重试到后端(AtomicUsize 纯内存,重启回默认)。 // GeneralPanel.vue onMounted 仅 Settings 页挂载,用户直接进 AI Chat 不同步 → 后端默认 10 与前端显示持久值不一致。 // clamp 与 GeneralPanel 语义对齐(0=不限透传,1-50 clamp;双保险,IPC 幂等值相同无害) // 包 try/catch 防 IPC 失败 reject onMounted(对齐 dataChangedListener 模式) @@ -201,8 +201,8 @@ onMounted(async () => { window.addEventListener('keydown', handleKeydown) // 加载知识库候选数(供侧栏 knowledge badge 显示) knowledgeStore.loadCandidates() - // AR-11:数据变更联动监听(AI 工具 create/update/delete 后端 emit df-data-changed → store listen 刷新对应列表,全局挂载因用户可在任意页) - // CR-260615-21:包 try/catch 防 Tauri listen 失败 reject onMounted 致 AR-11 静默失效 + // 数据变更联动监听(AI 工具 create/update/delete 后端 emit df-data-changed → store listen 刷新对应列表,全局挂载因用户可在任意页) + // 包 try/catch 防 Tauri listen 失败 reject onMounted 致 AR-11 静默失效 try { await projectStore.startDataChangedListener() } catch (e) { diff --git a/src/api/ai.ts b/src/api/ai.ts index 0d66ab6..5520826 100644 --- a/src/api/ai.ts +++ b/src/api/ai.ts @@ -43,7 +43,7 @@ export const aiApi = { }, /** - * 重新生成最后一条 AI 回复(UX-02)。 + * 重新生成最后一条 AI 回复。 * 后端:占用 generating → 弹出末尾 AI 回复(保留触发它的 user 消息)→ 重跑 agentic loop。 * 前端消费:与 sendMessage 一样需本地 push 空气泡占位 + 置 streaming(见 useAiSend.regenerate)。 */ @@ -56,7 +56,7 @@ export const aiApi = { }, /** - * 编辑最后一条 user 消息并重新生成(UX-09)。 + * 编辑最后一条 user 消息并重新生成。 * 后端:占用 generating → 替换末条 user content → 其后消息标 truncated(软删)→ 重跑 agentic loop。 * 前端消费:调 editMessage 前需移除被编辑消息之后的所有消息(视图截断)+ push 空气泡占位 + 置 streaming。 * 中间 user 消息编辑被后端拒绝(只允许末条)。 @@ -205,7 +205,7 @@ export const aiApi = { }, /** - * 一次性测试连接(P0-1):用**表单当前值**测试,不写库。 + * 一次性测试连接用**表单当前值**测试,不写库。 * - 新建态:传表单填的 providerType/baseUrl/apiKey,providerId=null。 * - 编辑态(apiKey 留空=不改):传 providerId 让后端从 DB 沿用旧密钥; * apiKey 非空则直接用表单新值。 @@ -329,13 +329,13 @@ export const aiApi = { return invoke('ai_conversation_archive', { conversationId, archived }) }, - /** 置顶/取消置顶对话(UX-17) */ + /** 置顶/取消置顶对话*/ setPinnedConversation(conversationId: string, pinned: boolean): Promise { return invoke('ai_conversation_set_pinned', { conversationId, pinned }) }, /** - /** 导出对话为指定格式(UX-18:对话导出;消费 batch46 ai_conversation_export IPC)。 + /** 导出对话为指定格式对话导出;消费 batch46 ai_conversation_export IPC)。 * 后端返回导出内容 String(markdown/json/txt),前端落 Blob 下载或复制剪贴板。 * @param format 'markdown' | 'json' | 'txt' */ diff --git a/src/api/idea.ts b/src/api/idea.ts index 694831c..1e015e2 100644 --- a/src/api/idea.ts +++ b/src/api/idea.ts @@ -5,7 +5,7 @@ export const ideaApi = { /** * 列出灵感。 * - * **双签名向后兼容**(F-260621-02): + * **双签名向后兼容** * - `list(status?)`:旧签名,仅状态过滤(等价 `list({ status })`)。 * - `list(query?)`:新签名,多条件(status/keyword/order_by/limit/offset)走后端 WHERE。 * diff --git a/src/api/project.ts b/src/api/project.ts index ad60c79..0b4fda9 100644 --- a/src/api/project.ts +++ b/src/api/project.ts @@ -65,7 +65,7 @@ export interface ProjectQuery { export const projectApi = { /** * 列出未删除项目。 - * F-260621-02:可选 query(关键词/排序/分页)。不传 = 当前全量行为(向后兼容)。 + * 可选 query(关键词/排序/分页)。不传 = 当前全量行为(向后兼容)。 */ list(query?: ProjectQuery): Promise { return invoke('list_projects', { query }) diff --git a/src/api/task.ts b/src/api/task.ts index c9b79cb..d071e64 100644 --- a/src/api/task.ts +++ b/src/api/task.ts @@ -5,7 +5,7 @@ export const taskApi = { /** * 列出任务。 * - * **双签名向后兼容**(F-260621-02): + * **双签名向后兼容** * - `list(projectId?)`:旧签名,仅项目过滤(等价 `list({ project_id: projectId })`)。 * - `list(query?)`:新签名,多条件(status/keyword/project_id/priority/assignee/...)走后端 WHERE。 * diff --git a/src/api/types.ts b/src/api/types.ts index 5e9879c..a8c7ab4 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -328,15 +328,15 @@ export interface AiProviderConfig { base_url: string default_model: string models: string | null - /** 模型能力配置数组(F-01:4 维度 + 路由控制;DB TEXT 列 JSON 解析,老库可能缺失) */ + /** 模型能力配置数组4 维度 + 路由控制;DB TEXT 列 JSON 解析,老库可能缺失) */ model_configs?: ModelConfig[] is_default: boolean config: string | null created_at: string updated_at: string - /** provider 是否进入负载均衡池(F-260614-04c)。false=仅配置存在,不参与主链路由/选池。老库默认 true。 */ + /** provider 是否进入负载均衡池(c)。false=仅配置存在,不参与主链路由/选池。老库默认 true。 */ enabled?: boolean - /** provider 在负载均衡池中的选择权重(0-100,F-260614-04c)。高权重优先被选为主;同权重近似轮询。老库默认 50。 */ + /** provider 在负载均衡池中的选择权重(0-100,c)。高权重优先被选为主;同权重近似轮询。老库默认 50。 */ weight?: number } @@ -353,7 +353,7 @@ export type Capability = 'tool_use' | 'embedding' | 'code_gen' /** 维度 3 — 价格分级(成本)。serde snake_case 字面量。'free' 已删(B-260618-05 死档,预设/启发式从不赋值)。 */ export type CostTier = 'low' | 'medium' | 'high' -/** 维度 4 — 智力分级。serde snake_case 字面量。CR-260618-11#3:与 CostTier 同,cost/intel 已从路由硬过滤(B-260618-03)及前端标签(UX-260618-04)去除,字段保留仅 DB 兼容,不再参与路由选模型。 */ +/** 维度 4 — 智力分级。serde snake_case 字面量。CR-260618-11#3:与 CostTier 同,cost/intel 已从路由硬过滤及前端标签去除,字段保留仅 DB 兼容,不再参与路由选模型。 */ export type IntelligenceTier = 'lite' | 'standard' | 'plus' | 'ultra' /** 探测来源(只读,由后端探测器填充)。serde snake_case 字面量。 */ @@ -384,7 +384,7 @@ export interface ModelConfig { } /** - * AI 错误分类字面量集(B-260615-42)。 + * AI 错误分类字面量集。 * 与后端 `commands::ai::ErrorType` 的 serde(rename_all = "snake_case") 严格对齐。 * 前端消费方 switch 于此常量集做差异化提示(auth 引导填 key / network 提示连接 / 等)。 */ @@ -458,7 +458,7 @@ export type AiChatEvent = ({ } | { type: 'AiHeartbeat' } | { - // F-260616-03: 达 max_iterations 暂停态(后端仍 generating=true),前端展示操作卡询问继续/停止 + // 达 max_iterations 暂停态(后端仍 generating=true),前端展示操作卡询问继续/停止 type: 'AiMaxRoundsReached' } | { // F-260619-03 Phase B: 路径授权弹窗(LLM 想访问白名单外目录,后端挂起 loop 等用户决定)。 @@ -480,7 +480,7 @@ export type AiChatEvent = ({ // enum 写入口(替代旧 bool 轨 add),消费方不再回退 streaming/generating bool 判断。 type: 'AiConvStateChanged'; conv_state: ConvState } | { - // F-260616-07: 流式调用自动重试提示(前端可在错误气泡内显示「重试 n/m」) + // 流式调用自动重试提示(前端可在错误气泡内显示「重试 n/m」) type: 'AiStreamRetry'; attempt: number; max_attempts: number } | { // F-15 上下文管理生命周期事件(原 useAiContext 独立监听器,现合入统一分发器) @@ -674,7 +674,7 @@ export interface AiToolCallInfo { result?: unknown /** 审批风险说明(AiApprovalRequired 时填充) */ reason?: string - /** AE-2025-03:write_file 行级 unified diff(旧文件 vs 新内容),审批卡即时预览。 + /** write_file 行级 unified diff(旧文件 vs 新内容),审批卡即时预览。 * 旧文件不存在(新建)或非 write_file 为 undefined,前端回退显新 content。 */ diff?: string /** @@ -729,7 +729,7 @@ export interface GoalEntry { status: 'active' | 'completed' } -/** 审批选择类型(F-260615-01: single=单选默认,multiple=多选) */ +/** 审批选择类型single=单选默认,multiple=多选) */ export type ApprovalSelectType = 'single' | 'multiple' /** 人工审批请求 */ @@ -739,7 +739,7 @@ export interface HumanApprovalRequest { title: string description: string options: string[] - /** F-260615-01: 缺省 single(向后兼容),multiple 时允许多选 */ + /** 缺省 single(向后兼容),multiple 时允许多选 */ select_type?: ApprovalSelectType } @@ -748,7 +748,7 @@ export interface HumanApprovalResponse { execution_id: string node_id: string decision: string - /** F-260615-01: 多选结果(single 时长度=1,multiple 时长度≥1) */ + /** 多选结果(single 时长度=1,multiple 时长度≥1) */ decisions?: string[] comment?: string } @@ -758,7 +758,7 @@ export interface AiConversationDetail { id: ConvId title: string | null messages: string // JSON string of ChatMessage[] - /** 生成中返回 true(后端 ai_conversation_switch 在 generating 时置),前端据此禁编辑(FR-C5) */ + /** 生成中返回 true(后端 ai_conversation_switch 在 generating 时置),前端据此禁编辑*/ readonly?: boolean } diff --git a/src/api/workflow.ts b/src/api/workflow.ts index 3b756fb..9629d42 100644 --- a/src/api/workflow.ts +++ b/src/api/workflow.ts @@ -7,7 +7,7 @@ export const workflowApi = { * 触发工作流执行,返回 execution_id。 * * F-260616-06 阶段2(②-2): taskId/targetStatus 可选,同时传时工作流完成/失败联动推进任务。 - * B-260615-34:Tauri 2 IPC 不转 camelCase,snake_case 对齐后端 workflow.rs。 + * Tauri 2 IPC 不转 camelCase,snake_case 对齐后端 workflow.rs。 */ run( name: string, @@ -46,8 +46,8 @@ export const workflowApi = { * 统一以 decisions 数组表达选择,按 select_type 判定单/多选: * - 单选:decision 取 decisions[0]; * - 多选:decisions 透传全量,decision 取首项作占位(后端按 decisions 走)。 - * options 透传闭环后端校验(R-PD-5)。 - * B-260615-34:Tauri 2 IPC 不转 camelCase,snake_case 对齐后端 workflow.rs。 + * options 透传闭环后端校验。 + * Tauri 2 IPC 不转 camelCase,snake_case 对齐后端 workflow.rs。 */ approveHumanApproval(params: { execution_id: string diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index 47415b2..564f651 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -162,7 +162,7 @@ const router = useRouter() const appSettings = useAppSettingsStore() const unifiedApproval = appSettings.useSetting('df-ai-unified-approval', true) -// ── UX-2025-20: 空状态示例问题随 MessageList 子组件迁移(examplePrompts 数组在子组件内); +// ── 空状态示例问题随 MessageList 子组件迁移(examplePrompts 数组在子组件内); // 父仅保留 sendExamplePrompt 转发(子 emit 'send-example-prompt' → 父委托 ChatInput.sendExamplePrompt)。── /** 跳 Settings 配置 provider:嵌入模式 router.push;分离窗口无 Settings 路由,降级 toast 提示 */ @@ -184,9 +184,9 @@ function sendExamplePrompt(i18nKey: string) { // i18n 不在本任务白名单,文案硬编码,后续补 aiChat.toastSendFail key const { toast, showToast } = useToast() -// B-260616-12: 工具慢执行提示事件 unlistener(onMounted 注册,onBeforeUnmount 释放) +// 工具慢执行提示事件 unlistener(onMounted 注册,onBeforeUnmount 释放) let _unlistenToolSlow: (() => void) | null = null -// AE-2025-04: 会话信任自动放行 toast unlistener(useAiEvents 经事件总线中转,同 _unlistenToolSlow 模式) +// 会话信任自动放行 toast unlistener(useAiEvents 经事件总线中转,同 _unlistenToolSlow 模式) let _unlistenAutoApproved: (() => void) | null = null // P1-e-d: 审批失败 toast unlistener(useAiApproval 审批 IPC 未送达经事件总线 emit,同 _unlistenToolSlow 模式) let _unlistenAiToast: (() => void) | null = null @@ -206,10 +206,10 @@ function onChatInputError(payload: { msg: string; type: 'error' | 'warning' | 'i // 第四批抽离: MessageList 子组件引用(expose scrollToBottom/scrollToFirstPending) const messageListRef = ref | null>(null) -// AE-2025-02:头部审批徽标 — 当前对话积压的待审批工具数(>0 时显示) +// 头部审批徽标 — 当前对话积压的待审批工具数(>0 时显示) const pendingApprovalCount = computed(() => store.state.pendingApprovals.length) -// AE-2025-07: Agentic 循环进度条 +// Agentic 循环进度条 // 仅 agentic 多轮(streaming 且 agentRound>0)显示;单轮问答不收 AiAgentRound 故恒隐藏。 const showAgenticProgress = computed(() => store.state.streaming && store.state.agentRound > 0, @@ -236,7 +236,7 @@ async function confirmDeleteConversation(id: string) { const conv = store.state.conversations.find(c => c.id === id) if (!conv) console.warn('confirmDeleteConversation: conv not found, id=', id) const title = conv?.title || t('aiChat.newConversation') - // UX-260617-24:删除当前活跃对话加醒目提示(通用文案不区分,活跃对话误删影响更大)。 + // 删除当前活跃对话加醒目提示(通用文案不区分,活跃对话误删影响更大)。 const isActive = id === store.state.activeConversationId const msgKey = isActive ? 'aiChat.confirmDeleteActiveConversation' : 'aiChat.confirmDeleteConversation' if (!await confirmDialog(t(msgKey, { title }))) return @@ -270,13 +270,13 @@ async function confirmNewConversation() { // projectsStore 仍由父持有(onMounted 预加载 projects/tasks 供子组件 @ 联想读取) const projectsStore = useProjectStore() -// UX-09:切换对话/新对话时取消编辑态(编辑态绑定末条 user,切走后无效) +// 切换对话/新对话时取消编辑态(编辑态绑定末条 user,切走后无效) // 2026-06-18 改:打开/切换会话默认进 specify 模式并选中权重最高 model(用户诉求:默认确定选中、 // 不随机/不摇摆)。enabledModels 已按 weight 降序,[0]=最高。空池(未拉取)落 null=auto 兜底。 // 后端 run_agentic_loop 对 override 兜底校验池内才用,前端预选无副作用。 // 第三批抽离: 默认 model 选中逻辑(modelOverride=enabledModels[0])随 enabledModels 迁移至 // TopBar.vue 的 activeConversationId watch;本组件仅保留编辑态取消。 -// 滚动边沿/跟随意图重置(SW-260618-18)随 MessageList 迁移,由子组件自管。 +// 滚动边沿/跟随意图重置随 MessageList 迁移,由子组件自管。 watch(() => store.state.activeConversationId, () => { if (editingMsgId.value) cancelEdit() }) @@ -291,13 +291,13 @@ const convSidebarRef = ref | null>(null // cycleProvider/providerBarVisible/showProviderSwitched 已迁移至 ai/TopBar.vue // (顶部工具栏 + provider/model 选择逻辑,零行为变更,store 单例共享 modelOverride)。 -/** UX-09:进入编辑态 — 置 editingMsgId(透传 ChatInput prop)+ 委托 ChatInput.startEditFocus 回填/聚焦。 */ +/** 进入编辑态 — 置 editingMsgId(透传 ChatInput prop)+ 委托 ChatInput.startEditFocus 回填/聚焦。 */ function startEdit(msg: AiMessage): void { editingMsgId.value = msg.id chatInputRef.value?.startEditFocus(msg) } -/** UX-09:取消编辑态(切换对话/清空/ESC)。清 editingMsgId + 委托 ChatInput.clearInput 清输入。 */ +/** 取消编辑态(切换对话/清空/ESC)。清 editingMsgId + 委托 ChatInput.clearInput 清输入。 */ function cancelEdit(): void { editingMsgId.value = null chatInputRef.value?.clearInput() @@ -366,7 +366,7 @@ function onGlobalKeydown(e: KeyboardEvent) { } /** - * UX-260617-16:未发送输入保护。检测 inputText/pendingImages/pendingSkill 任一非空, + * 未发送输入保护。检测 inputText/pendingImages/pendingSkill 任一非空, * 用于 beforeunload 拦截(分离窗口关闭/刷新)与未来 onBeforeUnmount 拦截(面板切换)。 */ function hasUnsentInput(): boolean { @@ -452,15 +452,15 @@ function scrollToBottom(smooth = false) { messageListRef.value?.scrollToBottom(smooth) } onBeforeUnmount(() => { - // 全局快捷键(UX-2025-07):卸载时移除 window listener,防分离窗口/切组件后泄漏 + // 全局快捷键卸载时移除 window listener,防分离窗口/切组件后泄漏 window.removeEventListener('keydown', onGlobalKeydown) - // UX-260617-16:摘除未发送输入保护(分离窗口关闭/页面刷新触发 beforeunload) + // 摘除未发送输入保护(分离窗口关闭/页面刷新触发 beforeunload) window.removeEventListener('beforeunload', onBeforeUnloadGuard) // 注:导出菜单外部点击监听/侧栏拖拽 listener/标题淡入计时器现由 ConversationSidebar // 子组件 onBeforeUnmount 自管(第一批抽离),父不再持有这些引用。 // 流式 rAF 清理(streaming watch 内 cancelAnimationFrame)随 MessageList 迁移,子组件 // onBeforeUnmount 自管(子 watch streaming 已注册,卸载自动停)。 - // CR-260615-24: 释放后端 listener(ai-chat-event/ai-conversation-changed)+清流式看门狗。 + // 释放后端 listener(ai-chat-event/ai-conversation-changed)+清流式看门狗。 // 之前 stopListener 定义但全仓零调用 → listener+watchdog 永久泄漏;分离窗口主窗口 AiChat // 卸载(走 App.vue v-if detach)时 listener 仍挂后端,watchdog 130s 后写已卸载 state。 store.stopListener() @@ -473,9 +473,9 @@ onBeforeUnmount(() => { }) onMounted(async () => { - // 全局快捷键(UX-2025-07):window 级 listener,卸载时移除(对齐 _unlistenToolSlow 生命周期) + // 全局快捷键window 级 listener,卸载时移除(对齐 _unlistenToolSlow 生命周期) window.addEventListener('keydown', onGlobalKeydown) - // UX-260617-16:未发送输入保护。AiChat 非路由组件(App.vue 内嵌 + AiDetached 路由), + // 未发送输入保护。AiChat 非路由组件(App.vue 内嵌 + AiDetached 路由), // beforeRouteLeave 不适用。分离窗口关闭/页面刷新时若有未发送 inputText/pendingImages/ // pendingSkill,经 beforeunload 拦截提示用户。注意:e.preventDefault() 才是现代浏览器 // 触发原生「确定离开?」的标准做法 returnValue 仅 Chrome 兼容旧写法。 @@ -496,13 +496,13 @@ onMounted(async () => { // 此处按需预加载(已加载则跳过), 不阻塞首屏(无 await, 后台执行)。 if (!projectsStore.projects.length) void projectsStore.loadProjects() if (!projectsStore.tasks.length) void projectsStore.loadTasks() - // B-260616-12: 监听工具慢执行提示(useAiEvents 经 Tauri 事件总线广播;composable 无组件上下文, + // 监听工具慢执行提示(useAiEvents 经 Tauri 事件总线广播;composable 无组件上下文, // 由本组件弹本地 toast。主窗口与分离窗口各挂一份 AiChat,各自消费,无重复——emit 单发, // 两窗口的 listener 各收一次但 msg 相同,toast 各自渲染互不干扰) _unlistenToolSlow = await listen<{ name: string }>('ai-tool-slow-toast', (e) => { showToast(t('aiTool.executionSlow', { name: e.payload.name }), 'warning') }) - // AE-2025-04: 会话信任自动放行 toast(轻量 info,非审批气泡,避免干扰主流程) + // 会话信任自动放行 toast(轻量 info,非审批气泡,避免干扰主流程) _unlistenAutoApproved = await listen<{ tool: string; dir: string }>('ai-tool-auto-approved-toast', (e) => { showToast(t('aiChat.autoApprovedToast', { tool: e.payload.tool, dir: e.payload.dir }), 'info') }) @@ -516,7 +516,7 @@ onMounted(async () => { // 恢复生成中态(刷新后/分离窗口接管): // - 分离模式(detached=true):resumeInDetached 接管主窗口当前对话 // - 主面板模式(detached=false):restoreGeneratingState 恢复主面板自身的生成态 - // (UX-260617-13: 主面板刷新后无等价恢复链 → streaming=false + 无占位气泡 + 后端 + // 主面板刷新后无等价恢复链 → streaming=false + 无占位气泡 + 后端 // 事件不进流式分支,退化为非流式。现对齐 resumeInDetached 同款恢复链)。 // 两者互斥(detached 三元决定),不会双重恢复;restoreGeneratingState 内幂等守卫(state.streaming 已 true 则跳过)。 // 在 startListener(L0 ai-client-ready 握手)之后调用,握手仍先于恢复逻辑生效。 @@ -691,7 +691,7 @@ async function initContextEventListeners(): Promise { } /* 第五批抽离: F-260616-03 max_iterations 操作卡样式已迁移至 ai/MaxRoundsCard.vue */ -/* ── AE-2025-07: Agentic 循环进度条 ── */ +/* ── Agentic 循环进度条 ── */ .ai-agentic-progress { display: flex; align-items: center; @@ -803,7 +803,7 @@ async function initContextEventListeners(): Promise { .ai-toast-error { background: var(--df-danger-bg); color: var(--df-danger); border: 0.5px solid var(--df-danger); } .ai-toast-warning { background: var(--df-warning-bg); color: var(--df-warning); border: 0.5px solid var(--df-warning); } .ai-toast-info { background: var(--df-accent-bg); color: var(--df-accent); border: 0.5px solid var(--df-accent); } -/* P0-2: ToastType 新增 'success' 兜底样式 */ +/* ToastType 新增 'success' 兜底样式 */ .ai-toast-success { background: var(--df-success-bg); color: var(--df-success); border: 0.5px solid var(--df-success); } .ai-toast-enter-active, .ai-toast-leave-active { transition: opacity 0.2s, transform 0.2s; } .ai-toast-enter-from, .ai-toast-leave-to { opacity: 0; transform: translate(-50%, -8px); } diff --git a/src/components/ToolCard.vue b/src/components/ToolCard.vue index 9f9915c..922dcbb 100644 --- a/src/components/ToolCard.vue +++ b/src/components/ToolCard.vue @@ -213,7 +213,7 @@ const isFailed = computed(() => { if (r && typeof r.exit_code === 'number') return r.exit_code !== 0 if (r) return true } - // A1-B5:git 只读工具失败返 {success:false,error}(结构化非 Err),通用分支识别 + // git 只读工具失败返 {success:false,error}(结构化非 Err),通用分支识别 if (r && r.success === false) return true if (!r) { const raw = typeof tc.result === 'string' ? tc.result : '' @@ -223,7 +223,7 @@ const isFailed = computed(() => { return false }) -/** AE-2025-03: write_file 审批 diff 行解析(复用 parseDiffLines 纯函数,数据源 props.tc.diff) */ +/** write_file 审批 diff 行解析(复用 parseDiffLines 纯函数,数据源 props.tc.diff) */ const diffLines = computed(() => parseDiffLines(props.tc.diff)) @@ -236,16 +236,16 @@ const diffLines = computed(() => parseDiffLines(props.tc.diff)) overflow: hidden; max-width: var(--df-msg-max-width); animation: fadeInUp 0.2s var(--df-ease); - /* UX-260617-14:相对定位锚定本卡 toast(absolute 子元素) */ + /* 相对定位锚定本卡 toast(absolute 子元素) */ position: relative; - /* UX-260616-03: border/shadow/opacity 过渡让折叠/失败态切换不突兀。 + /* border/shadow/opacity 过渡让折叠/失败态切换不突兀。 不做 body 高度动画(v-show display 切换 + flex gap 会留双倍间距致布局跳变,见 ToolCardList 注释)。 */ transition: border-color 0.2s var(--df-ease), box-shadow 0.2s var(--df-ease), opacity 0.2s var(--df-ease); } .ai-tool-card--pending_approval { border-color: rgba(240,199,94,0.3); box-shadow: 0 0 0 1px rgba(240,199,94,0.06); } .ai-tool-card--completed { border-color: rgba(61,219,160,0.2); } .ai-tool-card--rejected { border-color: rgba(240,101,101,0.2); opacity: 0.55; } -/* UX-260616-01: 工具失败态——AR-6 下 status 仍为 completed,故单独覆盖边框与 completed 区分 */ +/* 工具失败态——AR-6 下 status 仍为 completed,故单独覆盖边框与 completed 区分 */ .ai-tool-card--failed { border-color: rgba(240,101,101,0.35); box-shadow: 0 0 0 1px rgba(240,101,101,0.06); } .ai-tool-status-dot--failed { background: var(--df-danger); } .ai-tool-sub--failed { color: var(--df-danger); } @@ -326,7 +326,7 @@ const diffLines = computed(() => parseDiffLines(props.tc.diff)) border-radius: var(--df-radius-sm, 4px); } -/* BUG-260624-02: 审批挂起时长提示(>2分钟橙色,>5分钟红色) */ +/* 审批挂起时长提示(>2分钟橙色,>5分钟红色) */ .ai-tool-wait-time { padding: 3px 10px; margin-bottom: 6px; font-size: 11px; font-family: var(--df-font-mono); @@ -342,7 +342,7 @@ const diffLines = computed(() => parseDiffLines(props.tc.diff)) animation: pulseGlow 1s ease-in-out infinite; } -/* AE-2025-03: write_file 审批 diff 预览区(红删绿增行级) */ +/* write_file 审批 diff 预览区(红删绿增行级) */ .ai-tool-approval-diff { margin: 0 10px 8px; border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm, 4px); overflow: hidden; } .ai-tool-diff-pre { margin: 0; padding: 6px 0; max-height: 240px; overflow: auto; @@ -375,7 +375,7 @@ const diffLines = computed(() => parseDiffLines(props.tc.diff)) .ai-tool-btn--always:hover { background: var(--df-warning); color: #000; border-color: var(--df-warning); } .ai-tool-btn--reject { background: var(--df-danger-bg); color: var(--df-danger); } .ai-tool-btn--reject:hover { background: var(--df-danger); color: #fff; border-color: var(--df-danger); } -/* 审批 loading(B-260616-08):禁用两按钮防重复点击,spinner 转在原位 */ +/* 审批 loading禁用两按钮防重复点击,spinner 转在原位 */ .ai-tool-btn:disabled { opacity: 0.6; cursor: not-allowed; } .ai-tool-btn--approve:disabled:hover { background: var(--df-success-bg); color: var(--df-success); border-color: var(--df-border); } .ai-tool-btn--reject:disabled:hover { background: var(--df-danger-bg); color: var(--df-danger); border-color: var(--df-border); } @@ -385,7 +385,7 @@ const diffLines = computed(() => parseDiffLines(props.tc.diff)) } @keyframes aiToolBtnSpin { to { transform: rotate(360deg); } } -/* -- UX-260617-14: 审批 loading 超时兜底 toast(复用 AiChat 同款视觉 token) -- */ +/* -- 审批 loading 超时兜底 toast(复用 AiChat 同款视觉 token) -- */ .ai-tool-toast { position: absolute; top: 6px; left: 50%; transform: translateX(-50%); padding: 6px 12px; border-radius: var(--df-radius-sm); font-size: 11px; z-index: 100; diff --git a/src/components/ToolCardList.vue b/src/components/ToolCardList.vue index 851b986..fff7db7 100644 --- a/src/components/ToolCardList.vue +++ b/src/components/ToolCardList.vue @@ -81,12 +81,12 @@ const emit = defineEmits<{ const rootEl = ref() // 卡片级折叠(整卡 body 显隐,区别于 read_file 内容级) const expandedCards = ref(new Set()) -// UX-260616-03:用户主动展开的卡片(记忆态,不被自动收起清除)。 +// 用户主动展开的卡片(记忆态,不被自动收起清除)。 // 与 expandedCards 区分:expandedCards 是"展开"语义的当前态(含默认首卡), // userExpandedCards 是"用户明确点开"的持久标记,collapseInactive 跳过这些卡。 // 语义:用户读过的卡不强制折叠,符合"跟随阅读位置"的增强目标。 const userExpandedCards = ref(new Set()) -// UX-260617-23:用户主动折叠记忆(修复首卡 ci===0 兜底 return true 导致无法折叠)。 +// 用户主动折叠记忆(修复首卡 ci===0 兜底 return true 导致无法折叠)。 // isCardExpanded 最优先判断,覆盖默认首卡展开 + userExpandedCards 记忆。 const userCollapsedCards = ref(new Set()) // 内容级展开(read_file "展开全部") @@ -125,7 +125,7 @@ const collapsibleGroupCount = computed(() => groupedToolCalls.value.filter(g => g.calls.length >= 2).length ) -/** 首个可折叠分组索引(U-260618:全部展开/收起按钮下沉到此分组标题行尾巴) */ +/** 首个可折叠分组索引全部展开/收起按钮下沉到此分组标题行尾巴) */ const firstCollapsibleGi = computed(() => { for (let i = 0; i < groupedToolCalls.value.length; i++) { if (groupedToolCalls.value[i].calls.length >= 2) return i @@ -196,7 +196,7 @@ function toggleAllGroups(): void { /** * 分组显示名:工具名 → i18n 友好标签(英文化,原硬编码中文)。 - * UX-260617-17:用 aiTool.toolGroup.* 子键查表;未命中的工具回退 _ → 空格风格。 + * 用 aiTool.toolGroup.* 子键查表;未命中的工具回退 _ → 空格风格。 */ // tool.name → aiTool.toolGroup.* 子键(与后端 tool_registry 工具名一一对应) const TOOL_GROUP_I18N_KEY: Record = { @@ -248,14 +248,14 @@ function groupFileSummary(group: ToolCallGroup): string { * - 分组未折叠 → 用户手动 expandedCards 或首卡默认展开 * - 分组已折叠 → false(整组隐藏) * - 多卡片分组 + 未手动操作过 → 仅首卡(ci===0)展开 - * - UX-260616-03:userExpandedCards(用户主动展开记忆)优先,即使父级 collapseInactive + * - userExpandedCards(用户主动展开记忆)优先,即使父级 collapseInactive * 清了 expandedCards,记忆态仍保持展开(用户读过的卡不强制折叠)。 */ function isCardExpanded(tc: AiToolCallInfo, gi: number, ci: number): boolean { if (isGroupCollapsed(gi)) return false - // UX-260617-23:用户主动折叠最优先(覆盖默认首卡 ci===0 兜底展开 + 用户展开记忆) + // 用户主动折叠最优先(覆盖默认首卡 ci===0 兜底展开 + 用户展开记忆) if (userCollapsedCards.value.has(tc.id)) return false - // UX-260616-03:用户主动展开的卡片记忆态优先(不被自动收起影响) + // 用户主动展开的卡片记忆态优先(不被自动收起影响) if (userExpandedCards.value.has(tc.id)) return true // 用户手动切换过该卡(当前态) if (expandedCards.value.has(tc.id)) return true @@ -290,19 +290,19 @@ function toggleExpand(id: string) { /** * 收起不在 activeIds 中的已完成/已拒绝卡片(供父组件 auto-collapse watch 调用)。 - * UX-260616-03:userExpandedCards(用户主动展开记忆)不被清除——用户读过的卡 + * userExpandedCards(用户主动展开记忆)不被清除——用户读过的卡 * 即使不再 active(running/pending/write_file)也保持展开,避免"我刚才在看,突然被收起"。 * 仅清 expandedCards(默认态/首卡)与 expandedTools(内容级展开,跟随卡片折叠)。 */ function collapseInactive(activeIds: Set): void { - // CR-260615-31:对称过滤 expandedTools(原只清 expandedCards,expandedTools 残留致折叠后内容展开态) + // 对称过滤 expandedTools(原只清 expandedCards,expandedTools 残留致折叠后内容展开态) if (expandedCards.value.size > 0) { expandedCards.value = new Set([...expandedCards.value].filter(id => activeIds.has(id))) } if (expandedTools.value.size > 0) { expandedTools.value = new Set([...expandedTools.value].filter(id => activeIds.has(id))) } - // UX-260616-03:不清 userExpandedCards(记忆态),isCardExpanded 优先读它保持展开。 + // 不清 userExpandedCards(记忆态),isCardExpanded 优先读它保持展开。 } /** @@ -377,7 +377,7 @@ function groupIcon(name: string): string { gap: 8px; } -/* -- 顶部操作栏:批量审批 + 全局收起合并同行(UX-260616-02) -- */ +/* -- 顶部操作栏:批量审批 + 全局收起合并同行-- */ .ai-tool-topbar { display: flex; align-items: center; @@ -419,7 +419,7 @@ function groupIcon(name: string): string { } .ai-btn--danger:hover { filter: brightness(1.1); } -/* -- 全部展开/收起(U-260618:从 topbar 下沉到分组标题行,移除独立按钮 padding/背景, +/* -- 全部展开/收起从 topbar 下沉到分组标题行,移除独立按钮 padding/背景, 高度=文字行高,不撑高分组标题行;融入标题视觉,仅 hover 变色) -- */ .ai-tool-global-toggle { display: flex; @@ -447,7 +447,7 @@ function groupIcon(name: string): string { align-items: center; gap: 6px; padding: 6px 10px; - /* U-260618: max-width 对齐气泡/卡片(90%),不拉满 ai-msg-content 右侧 */ + /* max-width 对齐气泡/卡片(90%),不拉满 ai-msg-content 右侧 */ max-width: var(--df-msg-max-width); cursor: pointer; user-select: none; @@ -483,7 +483,7 @@ function groupIcon(name: string): string { color: var(--df-accent); } .ai-tool-group-chevron { - /* U-260618: 移除 margin-left:auto,让全部展开/收起按钮(global-toggle)占据行尾右对齐 */ + /* 移除 margin-left:auto,让全部展开/收起按钮(global-toggle)占据行尾右对齐 */ font-size: 10px; color: var(--df-text-dim); transition: transform 0.15s var(--df-ease); diff --git a/src/components/ToolResultBody.vue b/src/components/ToolResultBody.vue index 27e0571..a693894 100644 --- a/src/components/ToolResultBody.vue +++ b/src/components/ToolResultBody.vue @@ -277,7 +277,7 @@ const isFailed = computed(() => { if (r && typeof r.exit_code === 'number') return r.exit_code !== 0 if (r) return true } - // A1-B5: 可解析 JSON 且显式 success===false(git status/diff/log 只读工具失败返 + // 可解析 JSON 且显式 success===false(git status/diff/log 只读工具失败返 // {success:false,error} 合法 JSON 对象非 Err)→ 判失败,防绿框假成功 if (r && r.success === false) return true if (!r) { @@ -296,13 +296,13 @@ const cmdOutput = computed(() => { }) /** - * UX-260618-06: patch_file 结果 diff 行拆解(复用 parseDiffLines +add/-del/ ctx 着色)。 + * patch_file 结果 diff 行拆解(复用 parseDiffLines +add/-del/ ctx 着色)。 * 数据源:parsed.diff(patch_file 工具执行结果字段)。长 diff 截断到前 120 行防撑爆卡片。 */ const resultDiffLines = computed(() => parseDiffLines(parsed.value?.diff, 120)) /** - * grep 命中计数标签(F-260621):G-1 复用 useToolCard.formatGrep 单一函数, + * grep 命中计数标签G-1 复用 useToolCard.formatGrep 单一函数, * 消除原与 ToolResultBody 重复的三分支(files_with_matches/count/content 文案)。 * parsed 为 null 返空串,等价原逻辑;非 null 委托 formatGrep。 */ @@ -333,7 +333,7 @@ function onInstallObscura() { emit('install-obscura') } -// SW-260618-06: status 切到 completed 时按成功/失败初始化折叠态(失败默认展开便于看 stderr/错误 body)。 +// status 切到 completed 时按成功/失败初始化折叠态(失败默认展开便于看 stderr/错误 body)。 // immediate 时若初始非 completed,两 ref 保持默认 false,无副作用。 watch(() => props.tc.status, (s) => { if (s === 'completed') { @@ -402,7 +402,7 @@ watch(() => props.tc.status, (s) => { color: var(--df-warning); } -/* grep 结果(F-260621):文件栏复用 ai-tool-file-bar,命中行用等宽渲染 */ +/* grep 结果文件栏复用 ai-tool-file-bar,命中行用等宽渲染 */ .ai-tool-grep-result { border-top: 0.5px solid var(--df-border); } diff --git a/src/components/ai/ChatInput.vue b/src/components/ai/ChatInput.vue index 3341439..7a27260 100644 --- a/src/components/ai/ChatInput.vue +++ b/src/components/ai/ChatInput.vue @@ -128,7 +128,7 @@ import type { AiMessage, ContentPart, SkillInfo, ProjectRecord, TaskRecord, Idea import type { MentionItem } from './MentionItem' const props = defineProps<{ - /** UX-09: 编辑末条 user 消息态;父持有,handleSend 据此走 editMessage 而非 sendMessage */ + /** 编辑末条 user 消息态;父持有,handleSend 据此走 editMessage 而非 sendMessage */ editingMsgId: string | null }>() @@ -317,7 +317,7 @@ watch(inputText, (v) => { // / 技能联想(仅当输入以 / 开头且光标在首字符区) skillOpen.value = v.startsWith('/') if (skillOpen.value) skillIndex.value = 0 - // UX-10: @ 实体引用(技能联想与 @ 联想互斥,二者同时仅一开;视觉打架防护) + // @ 实体引用(技能联想与 @ 联想互斥,二者同时仅一开;视觉打架防护) if (skillOpen.value) { mentionOpen.value = false mentionStart.value = -1 @@ -327,7 +327,7 @@ watch(inputText, (v) => { }) /** - * UX-10: 检测光标前最近的 @ 触发符。 + * 检测光标前最近的 @ 触发符。 * 规则:从光标位置向前找最近的 @,期间无空白/换行则激活(支持 "abc @dev" 但不支持 "a @b c @d" 中第二个被空格阻断)。 * 找到 → mentionStart=该 @ 下标, mentionOpen=true; 否则关闭。 */ @@ -675,7 +675,7 @@ function selectMention(item: MentionItem) { } function handleKeydown(e: KeyboardEvent) { - // UX-09:编辑态 Escape 取消编辑(最高优先级) + // 编辑态 Escape 取消编辑(最高优先级) if (props.editingMsgId && e.key === 'Escape') { e.preventDefault() emit('cancel-edit') @@ -716,7 +716,7 @@ function handleKeydown(e: KeyboardEvent) { return } } - // UX-10: @ 实体引用浮层导航(仅有匹配项时拦截方向/回车;与技能联想互斥) + // @ 实体引用浮层导航(仅有匹配项时拦截方向/回车;与技能联想互斥) if (mentionOpen.value && mentionItems.value.length) { if (e.key === 'ArrowDown') { e.preventDefault() @@ -907,7 +907,7 @@ function focus(): void { } /** - * UX-260617-16:未发送输入保护。检测 inputText/pendingImages/pendingSkill 任一非空, + * 未发送输入保护。检测 inputText/pendingImages/pendingSkill 任一非空, * 用于 beforeunload 拦截(分离窗口关闭/刷新)与未来 onBeforeUnmount 拦截(面板切换)。 */ function hasUnsentInput(): boolean { diff --git a/src/components/ai/ConversationSidebar.vue b/src/components/ai/ConversationSidebar.vue index e595038..aadba71 100644 --- a/src/components/ai/ConversationSidebar.vue +++ b/src/components/ai/ConversationSidebar.vue @@ -322,7 +322,7 @@ function onActionsOutsideClick(e: MouseEvent) { } /** - * UX-260617-23:搜索结果点击切对话后清空 searchQuery,退出搜索模式回原时间分组视图。 + * 搜索结果点击切对话后清空 searchQuery,退出搜索模式回原时间分组视图。 * M3:命中项可能在折叠的时间分组或折叠的归档分组内,选中态不可见。切回分组视图后展开所在分组 * 并 scrollIntoView,确保选中项立即可见(原仅切 searchQuery,选中项落在折叠组里看不到高亮)。 */ @@ -425,7 +425,7 @@ const searchResults = computed( () => store.filteredConversations.value, ) -// ── UX-2025-16: 侧栏宽度拖拽 ── +// ── 侧栏宽度拖拽 ── const resizeState = { startX: 0, startWidth: 0 } function onResizeMove(e: MouseEvent) { store.setSidebarWidth(resizeState.startWidth + (e.clientX - resizeState.startX)) @@ -451,11 +451,11 @@ onMounted(() => { onBeforeUnmount(() => { document.removeEventListener('click', onActionsOutsideClick) - // UX-2025-16:拖拽中途卸载兜底摘临时 document listener(原 AiChat 同段迁移) + // 拖拽中途卸载兜底摘临时 document listener(原 AiChat 同段迁移) document.removeEventListener('mousemove', onResizeMove) document.removeEventListener('mouseup', onResizeEnd) document.body.classList.remove('ai-sidebar-resizing') - // UX-2025-20:释放标题淡入计时器 + // 释放标题淡入计时器 if (_titleFlashTimer) { clearTimeout(_titleFlashTimer); _titleFlashTimer = null } }) @@ -482,7 +482,7 @@ defineExpose({ height: 100%; background: var(--df-bg); } -/* UX-2025-16:右边缘 2px 拖拽条。hover/active 显高亮;拖拽中 body.ai-sidebar-resizing 全局锁 cursor 防抖 */ +/* 右边缘 2px 拖拽条。hover/active 显高亮;拖拽中 body.ai-sidebar-resizing 全局锁 cursor 防抖 */ .ai-sidebar-resize-handle { position: absolute; top: 0; @@ -662,7 +662,7 @@ body.ai-sidebar-resizing * { font-size: 9px; color: var(--df-text-dim); } -/* F-09: 多会话并发生成指示器(会话项标题行内脉冲点,多会话可同时显示) */ +/* 多会话并发生成指示器(会话项标题行内脉冲点,多会话可同时显示) */ .ai-conv-gen-dot { display: inline-block; width: 6px; @@ -706,7 +706,7 @@ body.ai-sidebar-resizing * { background: var(--df-sidebar-hover); color: var(--df-text); } -/* CR-22-1: 已置顶对话视觉强调(左边线,不依赖菜单展开,一眼识别)。 +/* 已置顶对话视觉强调(左边线,不依赖菜单展开,一眼识别)。 pinned 类挂在 .ai-conv-item 上(操作收进更多菜单后,按钮不再常驻,改 item 级类)。 */ .ai-conv-item--pinned { box-shadow: inset 2px 0 0 var(--df-accent, var(--df-text)); diff --git a/src/components/ai/DirAuthDialog.vue b/src/components/ai/DirAuthDialog.vue index 8495a2b..621845a 100644 --- a/src/components/ai/DirAuthDialog.vue +++ b/src/components/ai/DirAuthDialog.vue @@ -70,7 +70,7 @@ const { confirmState, confirmDialog, answerConfirm } = useConfirm() // 首轮慢响应(看门狗已超时清 streaming)之后——此时 streaming=false 但后端 generating=true、 // generatingConvs 含 conv(useAiEvents handleEvent 对非完成/错误事件 add)。若守卫仍 && streaming, // 挂起弹窗永不显示 → loop 无声卡死(用户发消息无响应)。去 streaming,isGenerating(conv) 单条件覆盖。 -// F-09: 多会话并发 — isGenerating(active) 替代单值比对 +// 多会话并发 — isGenerating(active) 替代单值比对 const isViewingGenerating = computed(() => store.isGenerating(store.state.activeConversationId), ) diff --git a/src/components/ai/EmptyState.vue b/src/components/ai/EmptyState.vue index ba2144d..49aa7de 100644 --- a/src/components/ai/EmptyState.vue +++ b/src/components/ai/EmptyState.vue @@ -20,7 +20,7 @@ const emit = defineEmits<{ (e: 'go-to-settings'): void }>() -// ── UX-2025-20: 空状态示例问题 ── +// ── 空状态示例问题 ── // 示例问题卡片(点击自动填入并发送);文案走 i18n key 数组,模板按 key 翻译。 const examplePrompts = [ 'aiChat.examplePrompt1', @@ -85,7 +85,7 @@ const examplePrompts = [ text-align: center; line-height: 1.5; } -/* UX-2025-20: 无 provider 引导 — 跳 Settings 按钮(克制:边框线 + accent 文字,不喧宾) */ +/* 无 provider 引导 — 跳 Settings 按钮(克制:边框线 + accent 文字,不喧宾) */ .ai-empty-action { display: inline-flex; align-items: center; diff --git a/src/components/ai/MaxRoundsCard.vue b/src/components/ai/MaxRoundsCard.vue index db958a5..ad2df7b 100644 --- a/src/components/ai/MaxRoundsCard.vue +++ b/src/components/ai/MaxRoundsCard.vue @@ -43,7 +43,7 @@ const { t } = useI18n() // 耗时长,130s 流式看门狗极易在迭代间隙超时清 streaming → 卡片因 streaming=false 不弹 → 用户看不到 // "继续/停止" → generating 永真卡死。达 max 比 DirAuthAuth 更易触发(必经多轮)。去 streaming, // isGenerating(conv) 单条件覆盖挂起态(达 max 时 generatingConvs 含 conv,useAiEvents handleEvent 对非完成/错误事件 add)。 -// F-09: 多会话并发 — isGenerating(active) 替代单值比对 +// 多会话并发 — isGenerating(active) 替代单值比对 const isViewingGenerating = computed(() => store.isGenerating(store.state.activeConversationId), ) diff --git a/src/components/ai/MessageList.vue b/src/components/ai/MessageList.vue index 3f78cd2..b23150d 100644 --- a/src/components/ai/MessageList.vue +++ b/src/components/ai/MessageList.vue @@ -28,7 +28,7 @@ import { formatRelative, formatDate } from '../../utils/time' import type { AiMessage } from '../../api/types' defineProps<{ - /** UX-09:当前正在编辑的消息 id(父持有,经 ChatInput 透传);末条 user 编辑按钮据此隐藏 */ + /** 当前正在编辑的消息 id(父持有,经 ChatInput 透传);末条 user 编辑按钮据此隐藏 */ editingMsgId?: string | null }>() @@ -42,8 +42,8 @@ const emit = defineEmits<{ const store = useAiStore() const { t } = useI18n() -// ═══ Markdown 渲染(ARC-260615-08:自研块级 memo,借鉴方案D流式核心) ═══ -// B-23:渲染基础设施(_marked/_purify/mdReady/_mdCache/loadMarkdown/escapeFallback/ +// ═══ Markdown 渲染自研块级 memo,借鉴方案D流式核心) ═══ +// 渲染基础设施(_marked/_purify/mdReady/_mdCache/loadMarkdown/escapeFallback/ // renderMd)抽至 composables/useMarkdown(模块级单例),与 TaskDetail/AiChat 共享。 // 流式核心(splitBlocks/parseBlock/renderStreamingBlocks/scheduleStreamParse)抽至 // composables/ai/useStreamRenderer(本组件经 opts 注入 saveSelection/restoreSelection/ @@ -54,14 +54,14 @@ const { t } = useI18n() // scheduleStreamParse/renderStreamingBlocks/clearStreamingState 均由 composable 返回。 const { loadMarkdown } = useMarkdown() -// ── UX-2025-20: 空状态示例问题(examplePrompts 已随 EmptyState.vue 提取) ── +// ── 空状态示例问题(examplePrompts 已随 EmptyState.vue 提取) ── const messagesContainer = ref() // 工具卡片列表(子组件 ToolCardList 自治折叠态,父级仅经 expose 调 collapseInactive 收起旧卡) const toolCardListRef = ref>() // 当前视图是否正在生成(切走后台生成时光标不显示) -// F-09: 多会话并发 — isGenerating(active) 替代单值比对 +// 多会话并发 — isGenerating(active) 替代单值比对 const isViewingGenerating = computed(() => store.state.streaming && store.isGenerating(store.state.activeConversationId) ) @@ -72,10 +72,10 @@ function isLastAi(msg: AiMessage): boolean { } /** - * B-260618-19: 是否渲染某条消息(供 .ai-msg-slot v-if 守卫)。 + * 是否渲染某条消息(供 .ai-msg-slot v-if 守卫)。 * * 背景:agentic 多轮下,LLM 某轮只发 tool_calls 不输出文本时(GLM anthropic_compat 常见), - * 该轮 assistant 消息 content='' + toolCalls=[...];变中间条后,旧 v-else-if(UX-260617-22) + * 该轮 assistant 消息 content='' + toolCalls=[...];变中间条后,旧 v-else-if * 漏判 toolCalls 把它整条过滤 → 工具卡片消失,且外层 .ai-msg-slot 恒渲染留空 div, * flex gap 堆积成大片空白,末条光标气泡随每轮 AiAgentRound push 往下移。 * @@ -207,14 +207,14 @@ async function handleMessagesScroll(): Promise { } watch(() => store.state.messages.length, onContentChange) -// SW-260618-07: currentText → onContentChange 合并到下方 scheduleStreamParse watch(同源 currentText,单 callback 顺序执行) +// currentText → onContentChange 合并到下方 scheduleStreamParse watch(同源 currentText,单 callback 顺序执行) // 流式 Markdown 渲染驱动(ARC-260615-08 块级 memo): // - currentText 变化(每 delta):rAF 节流 scheduleStreamParse,块级 memo 只重 parse 末块 // - streaming 翻转:结束清 rAF + 清 streamingBlocks(回 renderMd 走 final marked + 缓存) -// - mdReady 翻转(CR-260615-06):首屏 marked 慢 + 暂无新 delta 时末段停留 escapeFallback +// - mdReady 翻转首屏 marked 慢 + 暂无新 delta 时末段停留 escapeFallback // 纯文本;marked 就绪后主动重算一次,使纯文本转格式化 -// SW-260618-07: 合并 currentText 双 watch(原 :2040 onContentChange 滚动 + 本 watch scheduleStreamParse 分块)。 +// 合并 currentText 双 watch(原 :2040 onContentChange 滚动 + 本 watch scheduleStreamParse 分块)。 // 顺序:onContentChange(滚动跟随)先 → scheduleStreamParse(rAF 分块渲染)后,保持原注册顺序。 watch(() => store.state.currentText, (text) => { // 流式滚动跟随由 scheduleStreamParse 的 rAF 回调承接(DOM 高度就绪后滚), @@ -244,7 +244,7 @@ watch(() => store.state.streaming, (s) => { // streamingBlocks(依赖 currentText watch 的 !text 分支按新轮时机清,BUG-260624-01)。 cancelPendingRaf() if (s) { - // UX-260616-03: 新一轮生成开始 → 收起上一轮已完成的旧工具卡,聚焦当前生成内容。 + // 新一轮生成开始 → 收起上一轮已完成的旧工具卡,聚焦当前生成内容。 // 仅当前视图正在生成时触发(isGenerating(activeConversationId)), // 切走后台生成/恢复会话(streaming 初值已 true 的 onMounted 路径)不误触。 // buildActiveToolIds/collapseAllToolLists 是 function 声明,提升可用。 @@ -252,7 +252,7 @@ watch(() => store.state.streaming, (s) => { collapseAllToolLists(buildActiveToolIds(store.state.messages)) } } else { - // UX-2025-01: 流式→完成切换,模板从 v-for 分块切到 v-html 整段,DOM 重建 + // 流式→完成切换,模板从 v-for 分块切到 v-html 整段,DOM 重建 saveSelection() clearStreamingState() nextTick(() => restoreSelection()) @@ -270,7 +270,7 @@ watch(() => store.state.streaming, (s) => { // 只在 toolCall 状态翻转(新卡片/完成/拒绝)时触发,与折叠语义精确对齐。 // snap/MESSAGE_CAP 兜底机制保持不变(deep watch 仍做变化检测,只是 body 短路掉无意义重算)。 // -// UX-260616-03: 触发时机扩展。除原 messages deep watch(toolCall 状态翻转/新消息条数), +// 触发时机扩展。除原 messages deep watch(toolCall 状态翻转/新消息条数), // 另加 agentRound watch(AiAgentRound 新轮次 → 收起上一轮工具卡,聚焦当前轮)。 // active Set 推导逻辑复用 buildActiveToolIds,两 watch 共用。 function buildActiveToolIds(msgs: typeof store.state.messages): Set { @@ -330,7 +330,7 @@ watch( }, ) -// UX-260616-03: AiAgentRound 新轮次到达时收起上一轮工具卡(round 递增)。 +// AiAgentRound 新轮次到达时收起上一轮工具卡(round 递增)。 // 首次加载不触发(等真到达更"新"的轮次才收起,避免历史会话加载即折叠)。 watch( () => store.state.agentRound, @@ -340,12 +340,12 @@ watch( }, ) -// CR-260615-06: mdReady 就绪后主动触发首屏 Markdown 重渲染 +// mdReady 就绪后主动触发首屏 Markdown 重渲染 // (marked 加载慢 + 无新 delta 时末段停留纯文本,不触发模板重算) const _mdRenderKey = ref(0) function forceUpdate() { _mdRenderKey.value++ } -// ── UX-2025-01: 选文字保持(save/restore Selection) ── +// ── 选文字保持(save/restore Selection) ── // v-html 整体替换 innerHTML 会销毁子 DOM 节点 → Node 引用断连 → 选区丢失。 // 方案: 保存为「气泡内文本偏移量 + 选中文字」,重建后按偏移量重新定位。 // 偏移量对 Markdown 渲染结果稳定(同内容→同DOM文本节点序列→同偏移)。 @@ -484,7 +484,7 @@ watch(mdReady, (ready) => { } }) -// ── 消息气泡拷贝(F-260615-13) ── +// ── 消息气泡拷贝── // 必须在 click 事件内(或其同步调用链)调 clipboard API (浏览器安全限制) // navigator.clipboard 在非 secure context (Electron file:// / 旧版 webview) 不可用, // 回退到 execCommand('copy') 兜底;两者均失败时提示复制失败而非假成功。 @@ -558,7 +558,7 @@ async function copyMsgAsImage(msg: AiMessage): Promise { } } -// ── UX-03: 错误气泡操作入口 ── +// ── 错误气泡操作入口 ── // errorType 经 useAiEvents push 错误消息时附带(types.ts AiMessage 未含此字段, // 经 cast 读写闭环在 AiChat.vue / useAiEvents 两端,不污染 types.ts 定义)。 interface AiMessageWithErrorType extends AiMessage { @@ -572,7 +572,7 @@ function canOpenSettings(msg: AiMessage): boolean { } /** - * UX-260616-01: 错误是否可重试(显隐「重试」按钮)。 + * 错误是否可重试(显隐「重试」按钮)。 * Fatal 类错误(鉴权/Provider 配置错——4xx/401/403/invalid_api_key)重试必再败, * 隐藏重试按钮,引导用户走「去设置」。network/timeout/unknown 为瞬时错误,可重试。 * retryable 推断依据后端 error_type(本批前端推断;后端 AiStreamRetry/AiError 加 retryable 字段下批 df-ai 增强)。 @@ -587,7 +587,7 @@ function canRetry(msg: AiMessage): boolean { /** * 错误气泡「重试」:取错误前的末尾 user 消息重发。 * 复用 regenerate 链路(后端弹末尾 AI 回复——错误消息本身——取触发它的 user 消息重跑)。 - * 语义等价"重新生成",但语义场景是错误恢复,文案/入口独立(UX-03)。 + * 语义等价"重新生成",但语义场景是错误恢复,文案/入口独立。 */ async function handleRetry(): Promise { try { @@ -741,9 +741,9 @@ const renderItems = computed(() => { return items }) -// UX-09:切换对话/新对话时由父取消编辑态;滚动边沿/跟随意图重置在此自管(SW-260618-18)。 +// 切换对话/新对话时由父取消编辑态;滚动边沿/跟随意图重置在此自管。 watch(() => store.state.activeConversationId, () => { - // SW-260618-18: 切会话重置滚动边沿检测 + 跟随意图(新会话默认跟随底部) + // 切会话重置滚动边沿检测 + 跟随意图(新会话默认跟随底部) setFollowing(true) // BUG-260624-01(消息重叠根治·论证 workflow 完整性高优):MessageList 是持久单实例(AiChat.vue // 无 :key/v-if 卸载),实例级 streamingBlocks(:69)跨会话/跨轮残留是消息重叠主根因之一。 @@ -1043,7 +1043,7 @@ defineExpose({ flex-direction: column; gap: 14px; } -/* UX-2025-19: .ai-messages 的直接 flex 子(B-260618-01:虚拟滚动 sentinel 已移除, +/* .ai-messages 的直接 flex 子虚拟滚动 sentinel 已移除, 现仅作消息分组的语义容器,gap:14px 由 .ai-messages 承担)。 */ .ai-msg-slot { display: flex; flex-direction: column; } @@ -1148,7 +1148,7 @@ defineExpose({ color: var(--df-danger); border-color: rgba(240,101,101,0.3); } -/* B-260618-06:修正 UX-260617-21 的 display:block 方案——display:block 让 table 失去表格 +/* 修正 UX-260617-21 的 display:block 方案——display:block 让 table 失去表格 布局上下文,tr/td 脱离 table 上下文塌成块级堆叠丢列对齐(用户截图反馈测试报告四列表格错乱)。 正解:table 保持默认 display:table(列对齐恢复)+ max-width:100%;宽表格横向滚动交由气泡 容器 .ai-msg-bubble--ai.ai-md 的 overflow-x:auto 承载(CSS spec 中 table-display 下 @@ -1253,7 +1253,7 @@ defineExpose({ .ai-msg-chip--idea { background: rgba(255, 255, 255, 0.22); border: 1px solid rgba(255, 255, 255, 0.35); } .ai-msg-chip--skill { background: rgba(0, 0, 0, 0.18); } -/* ── 消息时间戳(UX-2025-13) ── */ +/* ── 消息时间戳── */ .ai-msg-time { font-size: 10px; line-height: 1; @@ -1268,7 +1268,7 @@ defineExpose({ align-self: flex-end; /* 用户消息:靠气泡右对齐 */ } -/* ── 消息气泡拷贝按钮(F-260615-13) ── */ +/* ── 消息气泡拷贝按钮── */ .ai-copy-btn { position: absolute; right: 4px; @@ -1299,7 +1299,7 @@ defineExpose({ right: 30px; } -/* ── UX-09: 末条 user 消息编辑按钮(hover 显示,回填输入框编辑重生成) ── */ +/* ── 末条 user 消息编辑按钮(hover 显示,回填输入框编辑重生成) ── */ .ai-msg-edit-btn { display: flex; align-items: center; @@ -1324,7 +1324,7 @@ defineExpose({ color: var(--df-text); } -/* ── UX-02/UX-03: 消息操作栏(hover 显示 / 错误气泡常显) ── */ +/* ── UX-02/消息操作栏(hover 显示 / 错误气泡常显) ── */ .ai-msg-actions { display: flex; align-items: center; @@ -1338,7 +1338,7 @@ defineExpose({ .ai-msg-actions--error { opacity: 1; } -/* UX-260616-01: Fatal 错误(不可重试)的提示文案——替代隐藏的「重试」按钮, +/* Fatal 错误(不可重试)的提示文案——替代隐藏的「重试」按钮, 说明为何无重试入口,引导走「去设置」。 */ .ai-msg-not-retryable { font-size: 10.5px; @@ -1492,7 +1492,7 @@ defineExpose({ background: var(--df-bg); } -/* ═══ F-15: 压缩摘要 system 块(compressed 原文已展开,摘要置顶提示发给 AI 的是它) ═══ +/* ═══ 压缩摘要 system 块(compressed 原文已展开,摘要置顶提示发给 AI 的是它) ═══ 用户诉求:压缩只后端省 token,界面不藏消息。原文 compressed 照常 user/ai 气泡展开, 摘要 system 块置顶 accent 提示,让用户知情"AI 看到的是摘要"。 */ .ai-msg-system { diff --git a/src/components/ai/TopBar.vue b/src/components/ai/TopBar.vue index eb7cc2d..2bd2e64 100644 --- a/src/components/ai/TopBar.vue +++ b/src/components/ai/TopBar.vue @@ -334,7 +334,7 @@ watch(() => store.state.activeConversationId, () => { } }) -// 切 provider 后模型 override 同步(BUG-2026-07-07:provider 切换后 header 模型名不刷新): +// 切 provider 后模型 override 同步provider 切换后 header 模型名不刷新): // - modelOverride 是模块级 ref(useAiSend)不持久化,切 provider 后仍持旧 provider 的 model_id, // 该 id 不在新 provider 池里 → 下拉显示错位(UI 显示 X,实际后端用路由默认 Y)。 // - 守卫:当前选择对新 provider 模型池无效(空/不在池)时,默认选权重最高模型。 @@ -400,7 +400,7 @@ const historyExpanded = ref(false) function scrollToMessage(msgId: string) { historyExpanded.value = false // 消息列表已有 data-msg-id 属性,直接 DOM 查询定位。 - // BUG-260802:msgId 由后端生成,可能含 CSS 选择器元字符(: . [ ] / 空格 等), + // msgId 由后端生成,可能含 CSS 选择器元字符(: . [ ] / 空格 等), // 模板拼 selector 会被选择器解析器当伪类/类名异常 → querySelector 抛错或返回 null。 // 用 CSS.escape 转义 msgId,确保任意字符串安全嵌入属性选择器。 // 旧浏览器无 CSS.escape 的兜底:encodeURIComponent + attr*= 模糊匹配(非精确,降级路径)。 @@ -542,18 +542,18 @@ async function removeGoal(i: number) { function cycleProvider() { const ps = store.state.providers if (ps.length < 2) return - // F-260616-10: activeProvider 为 null 时从 DB 默认(is_default)起算,避免 findIndex=-1 跳到 providers[0] + // activeProvider 为 null 时从 DB 默认(is_default)起算,避免 findIndex=-1 跳到 providers[0] const startId = store.state.activeProvider || ps.find(p => p.is_default)?.id || null const idx = startId ? ps.findIndex(p => p.id === startId) : -1 // idx=-1 且无默认 provider 时 fallback 到 0(极端边界,正常路径不会走到) const next = ps[(Math.max(idx, 0) + 1) % ps.length] store.setProvider(next.id) - // UX-2025-14: 切换反馈 — toast 提示当前 model + 临时 bar 展示 model 名(2s 后淡出)。 + // 切换反馈 — toast 提示当前 model + 临时 bar 展示 model 名(2s 后淡出)。 // toast 机制:复用父自管 reactive showToast(分离窗口无 App.vue 根 toast),经 emit 回父弹。 showProviderSwitched(next.name || next.id) } -// ── UX-2025-14: Provider 切换临时 bar ── +// ── Provider 切换临时 bar ── // 切换后展示当前 model 名小字,0.15s 淡入,2s 后淡出。本子组件自管 bar 显隐(ref + Transition), // toast 经 emit 回父(父持有 toast reactive 容器)。 const providerBarVisible = ref(false) @@ -567,7 +567,7 @@ function showProviderSwitched(modelName: string) { _providerBarTimer = setTimeout(() => { providerBarVisible.value = false }, 2000) } -// UX-2025-14:释放 provider 切换 bar 计时器(从 AiChat onBeforeUnmount 迁移,与 _providerBarTimer 同处) +// 释放 provider 切换 bar 计时器(从 AiChat onBeforeUnmount 迁移,与 _providerBarTimer 同处) onBeforeUnmount(() => { if (_providerBarTimer) { clearTimeout(_providerBarTimer); _providerBarTimer = null } }) @@ -604,7 +604,7 @@ onBeforeUnmount(() => { white-space: nowrap; max-width: 200px; } -/* AE-2025-02:审批徽标(待审批>0 时显于头部,点击跳首个审批卡) */ +/* 审批徽标(待审批>0 时显于头部,点击跳首个审批卡) */ .ai-approval-badge { display: inline-flex; align-items: center; @@ -1014,7 +1014,7 @@ onBeforeUnmount(() => { font-size: 11px; color: var(--df-danger); } -/* UX-2025-14: Provider 切换临时 bar(淡入淡出 0.15s) */ +/* Provider 切换临时 bar(淡入淡出 0.15s) */ .ai-provider-switch-bar { display: flex; align-items: center; diff --git a/src/components/dashboard/ActiveProjectsPanel.vue b/src/components/dashboard/ActiveProjectsPanel.vue index ed57752..e74dbd3 100644 --- a/src/components/dashboard/ActiveProjectsPanel.vue +++ b/src/components/dashboard/ActiveProjectsPanel.vue @@ -114,7 +114,7 @@ const displayProjects = computed(() => .dot-release { background: var(--df-success); } .dot-planning { background: var(--df-info); } .dot-cancelled { background: var(--df-text-dim); } -/* WK-1:paused 置灰(对齐 cancelled dim 档,临时暂停不误标为已取消) */ +/* paused 置灰(对齐 cancelled dim 档,临时暂停不误标为已取消) */ .dot-paused { background: var(--df-text-dim); } .project-name { diff --git a/src/components/ideas/IdeaDetail.vue b/src/components/ideas/IdeaDetail.vue index b507ce4..1048e8b 100644 --- a/src/components/ideas/IdeaDetail.vue +++ b/src/components/ideas/IdeaDetail.vue @@ -322,7 +322,7 @@ const emit = defineEmits<{ (e: 'promote'): void (e: 'delete'): void (e: 'status-change', value: IdeaStatus): void - // WK-16:返 Promise 让子组件 await 知成功/失败。 + // 返 Promise 让子组件 await 知成功/失败。 // true=落库成功(关编辑态);false=失败(保持编辑态,toast 由父组件处理)。 (e: 'update-desc', value: string): Promise (e: 'update-related', ids: string[]): Promise @@ -344,7 +344,7 @@ function startEdit() { } // P1-⑥ saveEdit 改异步:emit 返回 Promise(父组件 store.updateIdea 落库结果), -// 仅成功时关编辑态;失败保持编辑态 + toast 由父组件处理(WK-16)。 +// 仅成功时关编辑态;失败保持编辑态 + toast 由父组件处理。 async function saveEdit() { if (savingDesc.value) return savingDesc.value = true @@ -392,7 +392,7 @@ watch(() => props.idea.description, () => void nextTick(measureDesc)) // P1-④ 状态切换 select → badge 点击:点击当前 badge 展开/收起下拉菜单, // 选中项后 emit('status-change') 交父组件确认+落库(沿用既有契约,不破坏 statusOptions 来源)。 const statusMenuOpen = ref(false) -// WK-5:状态下拉排除 promoted(立项走独立 promoteToProject 流程,列表状态菜单同排除, +// 状态下拉排除 promoted(立项走独立 promoteToProject 流程,列表状态菜单同排除, // 见 Ideas.vue onStatusChange 兜底);statusOptions 仍含 promoted 供 label 映射(已转立项的灵感 badge 正常显示)。 const menuStatusOptions = computed(() => props.statusOptions.filter(s => s.value !== 'promoted')) function closeStatusMenu() { @@ -502,7 +502,7 @@ async function saveTagsEdit() { savingTags.value = true try { const tags = tagsEditInput.value.split(',').map(t => t.trim()).filter(Boolean) - // WK-16:emit 返回落库结果,失败保持编辑态(成功才关),toast 由父组件处理 + // emit 返回落库结果,失败保持编辑态(成功才关),toast 由父组件处理 const ok = await emit('update-tags', JSON.stringify(tags)) if (ok) tagsEditing.value = false await nextTick() @@ -520,9 +520,9 @@ function statusLabelKey(status: IdeaStatus): string { return props.statusOptions.find(s => s.value === status)?.labelKey ?? status } -// B-260615-25:灵感描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例), +// 灵感描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例), // useRendered 封装 computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热) -// BUG-2026-07-08:原漏解构 ensureLoaded + onMounted 未调用,冷启动(marked 未被其他组件预热) +// 原漏解构 ensureLoaded + onMounted 未调用,冷启动(marked 未被其他组件预热) // 时 mdReady 永远 false,renderMd 走 escapeFallback 纯文本降级,Markdown 语法不渲染。 const { rendered: renderedDesc, ensureLoaded: ensureMdLoaded } = useRendered( () => props.idea.description ?? '', @@ -548,7 +548,7 @@ interface AdversarialEval { // 对抗式评估数据持久化在 ai_analysis JSON 字段中,前端解析渲染 // -// WK-15:旧实现直接 JSON.parse 后强转 AdversarialEval,字段缺失/非数值时 +// 旧实现直接 JSON.parse 后强转 AdversarialEval,字段缺失/非数值时 // 模板对 undefined*100 调 .toFixed 显 NaN,assessmentClass 对 undefined 调 // .toLowerCase() 崩。这里在解析层补齐数值默认值(防御式归一化),模板直读不二次判空: // - 数值字段缺失/非数值 → 0 @@ -599,7 +599,7 @@ const localAssessmentLabel = (recommendation: string): string => assessmentLabel(t, recommendation) function sentimentClass(sentiment: number) { - // 统一三档(FR-C2: 原 >=0 与模板 >0 矛盾,net_sentiment=0 时文案负面样式 positive) + // 统一三档原 >=0 与模板 >0 矛盾,net_sentiment=0 时文案负面样式 positive) if (sentiment > 0) return 'positive' if (sentiment < 0) return 'negative' return 'neutral' diff --git a/src/components/layout/AppLayout.vue b/src/components/layout/AppLayout.vue index 2335142..a71f986 100644 --- a/src/components/layout/AppLayout.vue +++ b/src/components/layout/AppLayout.vue @@ -106,7 +106,7 @@ const resizing = ref(false) // maximized=全宽(flex:1填充) | sidebar=固定宽 const panelStyle = computed(() => { if (aiStore.state.maximized) return { flex: '1', minWidth: '0' } - // LW-11:内联 width 用 min(panelWidth, 100vw) —— 小屏 media query 的 width:100vw 会被内联样式 + // 内联 width 用 min(panelWidth, 100vw) —— 小屏 media query 的 width:100vw 会被内联样式 // 更高优先级覆盖致失效;min() 让窄视口下宽度钳制到 100vw,media query 的 absolute 定位/层级仍生效 return { width: `min(${panelWidth.value}px, 100vw)`, diff --git a/src/components/layout/AppSidebar.vue b/src/components/layout/AppSidebar.vue index de19490..8ae6485 100644 --- a/src/components/layout/AppSidebar.vue +++ b/src/components/layout/AppSidebar.vue @@ -92,7 +92,7 @@ const appSettings = useAppSettingsStore() // 展开 192px(文字+图标) / 收缩 56px(仅图标,hover tooltip 显示文字) // 优先级:用户手动设置 > 小窗口自动收缩(≤768px) const COLLAPSE_THRESHOLD = 768 -// LW-10:useSetting 响应式读(loadAll 异步填充缓存后 watch 自动同步刷新)—— +// useSetting 响应式读(loadAll 异步填充缓存后 watch 自动同步刷新)—— // 原 onMounted 用 appSettings.get 同步读早于父级 loadAll 完成,重启后偏好丢失 const userCollapsed = appSettings.useSetting('df-sidebar-collapsed', false) const isNarrowWindow = ref(false) // 小窗口自动收缩标记 @@ -119,7 +119,7 @@ function handleCollapseKeydown(e: KeyboardEvent) { } onMounted(async () => { - // LW-10:收缩偏好由 useSetting 响应式恢复(loadAll 填充缓存后 watch 自动同步),移除 onMounted 同步读 + // 收缩偏好由 useSetting 响应式恢复(loadAll 填充缓存后 watch 自动同步),移除 onMounted 同步读 // 检查当前窗口宽度 checkWindowWidth() window.addEventListener('resize', checkWindowWidth) diff --git a/src/components/project/ApprovalDialog.vue b/src/components/project/ApprovalDialog.vue index 635b9e8..1a3759b 100644 --- a/src/components/project/ApprovalDialog.vue +++ b/src/components/project/ApprovalDialog.vue @@ -135,7 +135,7 @@ async function handleApproval(decision: string) { } } -// F-260615-01: 多选审批(UX-260618-17 submitting 防双击保留) +// 多选审批(UX-260618-17 submitting 防双击保留) async function handleApprovalMulti() { if (multiDecisions.value.length === 0) return submitting.value = true @@ -153,7 +153,7 @@ async function handleApprovalMulti() { } } -// F-260615-01: 每次新审批清空多选状态(避免上轮残留) +// 每次新审批清空多选状态(避免上轮残留) watch(() => pendingApproval.value, (newApproval) => { if (newApproval) { multiDecisions.value = [] diff --git a/src/components/settings/AllowedDirsPanel.vue b/src/components/settings/AllowedDirsPanel.vue index bdb623f..5906957 100644 --- a/src/components/settings/AllowedDirsPanel.vue +++ b/src/components/settings/AllowedDirsPanel.vue @@ -86,7 +86,7 @@ import SettingRow from './SettingRow.vue' const { t } = useI18n() const appSettings = useAppSettingsStore() -// F-260620: 接共享 toast 通道(Settings.vue showToast),替代 console.error/warn 用户零反馈 +// 接共享 toast 通道(Settings.vue showToast),替代 console.error/warn 用户零反馈 const emit = defineEmits<{ (e: 'toast', msg: string, type: 'error' | 'warning' | 'info' | 'success'): void }>() // F-260619-03 Phase A: AI 工具文件访问授权目录白名单 UI @@ -126,7 +126,7 @@ async function pickDir() { const selected = await open({ directory: true, multiple: false }) if (!selected || Array.isArray(selected)) return newDir.value = selected as string - // F-260620: 选完即加(消除选目录后需二次点"添加"的摩擦) + // 选完即加(消除选目录后需二次点"添加"的摩擦) await addDir() } catch (e) { console.error('选择目录失败:', e) diff --git a/src/components/settings/ConnectionPanel.vue b/src/components/settings/ConnectionPanel.vue index 8468471..48b115d 100644 --- a/src/components/settings/ConnectionPanel.vue +++ b/src/components/settings/ConnectionPanel.vue @@ -88,7 +88,7 @@ const { t } = useI18n() const appSettings = useAppSettingsStore() const emit = defineEmits<{ - // P0-2: 对齐 ToastType 加 'success'(本面板暂未用,保类型一致) + // 对齐 ToastType 加 'success'(本面板暂未用,保类型一致) (e: 'toast', msg: string, type: 'error' | 'warning' | 'info' | 'success'): void }>() diff --git a/src/components/settings/PerformanceSection.vue b/src/components/settings/PerformanceSection.vue index 0739572..90b1d33 100644 --- a/src/components/settings/PerformanceSection.vue +++ b/src/components/settings/PerformanceSection.vue @@ -67,9 +67,9 @@ const appSettings = useAppSettingsStore() const settings = reactive({ llmGlobalConcurrency: appSettings.get('df-llm-global-concurrency', 3), llmPerConvConcurrency: appSettings.get('df-llm-per-conv-concurrency', 2), - // F-260616-01: Agentic 循环最大轮次,默认 10(与后端 DEFAULT_MAX_AGENT_ITERATIONS 对齐) + // Agentic 循环最大轮次,默认 10(与后端 DEFAULT_MAX_AGENT_ITERATIONS 对齐) agentMaxIterations: appSettings.get('df-ai-agent-max-iterations', 10), - // F-260616-07: 流式失败重试次数,默认 3(与后端 DEFAULT_MAX_AGENT_RETRIES 对齐) + // 流式失败重试次数,默认 3(与后端 DEFAULT_MAX_AGENT_RETRIES 对齐) agentMaxRetries: appSettings.get('df-ai-agent-max-retries', 3), }) @@ -148,7 +148,7 @@ function onRetryChange() { } // 同步 Agentic 最大轮次到后端(debounce 300ms) -// F-260616-01: 后端 loop 入口 load 快照锁定边界——热改后当前 loop 不受影响, +// 后端 loop 入口 load 快照锁定边界——热改后当前 loop 不受影响, // 下次发消息才生效(与 llm_concurrency 传 Arc 实时反映的区别)。 // 双 clamp:此处 1-50 + 后端 command 再 clamp 1-50,防越界输入致 loop 失效/失控 let _agentIterTimer: ReturnType | null = null @@ -184,7 +184,7 @@ const unlimited = computed({ }) // 同步流式失败重试次数到后端(debounce 300ms) -// F-260616-07: 后端 loop 入口 load 快照锁定边界——热改后当前 loop 不受影响, +// 后端 loop 入口 load 快照锁定边界——热改后当前 loop 不受影响, // 下次发消息才生效。双 clamp:此处 0-10 + 后端 command 再 clamp 0-10 let _agentRetryTimer: ReturnType | null = null function syncAgentMaxRetries() { @@ -206,12 +206,12 @@ function syncAgentMaxRetries() { onMounted(() => { // 启动同步一次 LLM 并发配置(防刷新页面后后端未恢复用户设定值) syncConcurrencyConfig() - // F-260616-01: 启动同步一次 Agentic 最大轮次(防刷新页面后后端未恢复用户设定值) - // F-260618-23: App.vue 根 onMounted 已在应用启动时恢复(覆盖用户没先进 Settings 直接用 AI Chat 的场景), + // 启动同步一次 Agentic 最大轮次(防刷新页面后后端未恢复用户设定值) + // App.vue 根 onMounted 已在应用启动时恢复(覆盖用户没先进 Settings 直接用 AI Chat 的场景), // 此处保留双保险(IPC 幂等值相同无害) syncAgentMaxIterations() - // F-260616-07: 启动同步一次流式失败重试次数(防刷新页面后后端未恢复用户设定值) - // F-260618-23: 同上,App.vue 根 onMounted 已恢复,此处双保险保留 + // 启动同步一次流式失败重试次数(防刷新页面后后端未恢复用户设定值) + // 同上,App.vue 根 onMounted 已恢复,此处双保险保留 syncAgentMaxRetries() }) diff --git a/src/components/settings/ProviderPanel.vue b/src/components/settings/ProviderPanel.vue index 602c424..a61c595 100644 --- a/src/components/settings/ProviderPanel.vue +++ b/src/components/settings/ProviderPanel.vue @@ -185,7 +185,7 @@ import type { AiProviderConfig, ModelConfig, Modality, Capability, ProbeSource } import { PROVIDER_PRESETS, CUSTOM_PRESET_ID, findPreset } from './providerPresets' // ============================================================ -// AI 提供商域 — 真实后端 CRUD + F-04c 负载均衡池(enabled / weight) +// AI 提供商域 — 真实后端 CRUD + c 负载均衡池(enabled / weight) // ============================================================ const { t } = useI18n() @@ -200,7 +200,7 @@ async function syncAiStoreProviders() { } const emit = defineEmits<{ - // P0-2: 加 'success'(保存成功绿色 toast) + // 加 'success'(保存成功绿色 toast) (e: 'toast', msg: string, type: 'error' | 'warning' | 'info' | 'success'): void (e: 'providers-changed'): void }>() @@ -229,7 +229,7 @@ const providerForm = reactive({ // F-01 阶段6:模型拉取 + 列表 fetching: false, models: [] as ModelConfig[], - // P0-2: 保存失败的持久错误横幅(非自隐 toast)。 + // 保存失败的持久错误横幅(非自隐 toast)。 // toast 3s 自隐易被忽略,保存失败时表单内常驻错误条 + 可操作建议, // 直到下次保存尝试/重开表单才清除。null=无错误。 saveError: null as string | null, @@ -401,7 +401,7 @@ function openProviderForm(p?: AiProviderConfig) { providerForm.models = [] } providerForm.fetching = false - providerForm.saveError = null // P0-2: 重开表单清旧错误横幅 + providerForm.saveError = null // 重开表单清旧错误横幅 providerForm.visible = true // L19/M32: 记初始快照(在 visible=true、字段回填完成后),isFormDirty 据此比对。 providerForm.savedSnapshot = formBusinessSnapshot() @@ -426,7 +426,7 @@ function onPresetChange() { } /** - * 测试连接按钮可用条件(P0-1): + * 测试连接按钮可用条件 * - baseUrl 必填(无论新建/编辑,表单值都要有) * - 新建态(editId 空):api_key 必填(无已保存配置可沿用) * - 编辑态(editId 有):api_key 可空(留空=沿用已保存密钥) @@ -438,7 +438,7 @@ const canTestConnection = computed(() => { }) /** - * 测试连接并拉取模型列表(P0-1)。 + * 测试连接并拉取模型列表。 * 用**表单当前值**测试(非 DB 值):新建态直接传表单值;编辑态传表单值 + editId, * apiKey 留空时后端从已保存配置沿用密钥。一次性测试不写库。 * @@ -486,7 +486,7 @@ async function fetchModels() { } /** - * P0-2: 把后端返回的错误文本分类成「用户可操作的建议」。 + * 把后端返回的错误文本分类成「用户可操作的建议」。 * * 后端 commands/provider.rs 已对密钥相关失败返回友好中文 Err(含原因+建议), * 这里只做轻量分类挂上"建议下一步",避免裸 Err 文本吓到用户: @@ -526,14 +526,14 @@ async function saveProvider() { defaultModel: providerForm.defaultModel, modelConfigs: providerForm.models, }) - // P0-2: 成功明确反馈——清错误横幅 + 关表单 + 绿色「已保存」toast + // 成功明确反馈——清错误横幅 + 关表单 + 绿色「已保存」toast providerForm.saveError = null providerForm.visible = false providerForm.savedSnapshot = '' // L19: 关表单清快照 await loadProviders() emit('toast', t('settings.toastSaved'), 'success') } catch (e) { - // P0-2: 失败时①表单保持编辑态(不关)②表单内常驻错误横幅(分类建议,非自隐) + // 失败时①表单保持编辑态(不关)②表单内常驻错误横幅(分类建议,非自隐) // ③仍发 error toast(用户视线不在表单时仍可见),双通道确保反馈不被忽略。 const raw = errMsg(e) console.error(t('settings.toastSaveFail', { msg: raw }), e) @@ -570,10 +570,10 @@ async function setDefaultProvider(id: string) { } // ============================================================ -// F-260614-04c: 负载均衡池可编辑层(enabled / weight) +// c: 负载均衡池可编辑层(enabled / weight) // 卡片即时调 IPC(对齐 syncConcurrencyConfig 模式);落库后后端 reload_provider_caps // 重建 per_provider caps,下条消息即按新配置 acquire。 -// 不走 saveProvider(全量 INSERT OR REPLACE 会触发 R-PD-1 密钥迁移分支,与 F-04c 轻量 UPDATE 路径冲突)。 +// 不走 saveProvider(全量 INSERT OR REPLACE 会触发 R-PD-1 密钥迁移分支,与 c 轻量 UPDATE 路径冲突)。 // ============================================================ let _poolTimer: ReturnType | null = null @@ -693,7 +693,7 @@ onMounted(loadProviders) } .detail-value.mask { color: var(--df-text-dim); letter-spacing: 1px; } -/* Provider 卡片 disabled 态(F-260614-04c:enabled=false 视觉弱化) */ +/* Provider 卡片 disabled 态(c:enabled=false 视觉弱化) */ .provider-card--disabled { opacity: 0.6; } .pool-badge { font-size: 10px; @@ -718,7 +718,7 @@ onMounted(loadProviders) .form-field-hint { flex-basis: 100%; margin-left: 82px; font-size: 11px; color: var(--df-text-dim); line-height: 1.5; } .form-actions { display: flex; justify-content: flex-end; padding-top: 4px; } -/* P0-2: 保存失败持久错误横幅(常驻 form-actions 上方,醒目于自隐 toast) */ +/* 保存失败持久错误横幅(常驻 form-actions 上方,醒目于自隐 toast) */ .save-error-banner { background: var(--df-danger-bg); border: 0.5px solid var(--df-danger); diff --git a/src/components/settings/providerPresets.ts b/src/components/settings/providerPresets.ts index 9896512..2f19fa4 100644 --- a/src/components/settings/providerPresets.ts +++ b/src/components/settings/providerPresets.ts @@ -60,7 +60,7 @@ export const PROVIDER_PRESETS: readonly ProviderPreset[] = [ baseUrl: 'https://api.deepseek.com', defaultModel: 'deepseek-v4-flash', // 重要:deepseek-chat / deepseek-reasoner 旧名已于 2026-07-24 停用, - // 现分别指向 deepseek-v4-flash 的非思考/思考模式(V4-Flash 2026-07-31 发布)。 + // 现分别指向 deepseek-v4-flash 的非思考/思考模式(lash 2026-07-31 发布)。 // 思考模式改 deepseek-v4-flash(传 thinking 参数)或 deepseek-v4-pro。 }, { @@ -74,7 +74,7 @@ export const PROVIDER_PRESETS: readonly ProviderPreset[] = [ baseUrl: 'https://token.sensenova.cn/v1', // 官方文档(platform.sensenova.cn/docs)核实:Token Plan API 仅 3 个模型 —— // sensenova-6.7-flash-lite(多模态对话主力)/ sensenova-u1-fast(图像生成,不能对话)/ deepseek-v4-flash(纯文本对话)。 - // 无 U1 / U1-Pro 对话模型(那是官网产品名,API 不提供);旧 SenseNova-Turbo 已退役。 + // 无 U1 / ro 对话模型(那是官网产品名,API 不提供);旧 SenseNova-Turbo 已退役。 // 默认填对话主力 sensenova-6.7-flash-lite(文档所有工具接入示例的 Model ID)。 defaultModel: 'sensenova-6.7-flash-lite', }, diff --git a/src/composables/ai/aiShared.ts b/src/composables/ai/aiShared.ts index 92ce2d4..a6f06ca 100644 --- a/src/composables/ai/aiShared.ts +++ b/src/composables/ai/aiShared.ts @@ -241,7 +241,7 @@ export function setConvState(convId: string | null | undefined, s: ConvState): v // ── F-09 B 路线 per-conv 流式态(streaming/currentText) ── // -// 背景(DEC-07a 父④):原 `state.streaming`(bool)/`state.currentText`(string)是 store 全局单例, +// 背景(a 父④):原 `state.streaming`(bool)/`state.currentText`(string)是 store 全局单例, // F-09 多会话并发下:A 后台生成中切到 B 会话,B 末条 AI 气泡命中 `isLastAi && streaming && // currentText` 会渲染 A 残留 currentText(BUG-260624-01 同源根因)。本批改 per-conv Map,各会话 // 独立累积流式文本/流式开关,切会话即切到该会话的 stream state,真并发不串话。 diff --git a/src/composables/ai/useAiApproval.ts b/src/composables/ai/useAiApproval.ts index 0b38411..9e8ce8c 100644 --- a/src/composables/ai/useAiApproval.ts +++ b/src/composables/ai/useAiApproval.ts @@ -22,7 +22,7 @@ import { findToolCall } from './aiShared' import { dispatchApprovalIPC } from './useToolApproval' /** - * F-260616-06: 审批按钮防抖守卫——同一 id 短期多次点击只发一次 IPC。 + * 审批按钮防抖守卫——同一 id 短期多次点击只发一次 IPC。 * IPC 往返 <100ms,300ms 窗口足够覆盖竞态。 */ const _pendingApprovalIds = new Set() @@ -30,7 +30,7 @@ const _pendingApprovalIds = new Set() /** * 工具审批:保持 pending_approval 让审批卡片可见(按钮 loading 由 ToolCard 本地 ref 持有), * IPC 失败时改 completed+错误文案。后端回事件后由 useAiEvents 转 completed/rejected。 - * B-260616-08:不再乐观置 running——原写法让 .ai-tool-approval 整块消失(切骨架屏), + * 不再乐观置 running——原写法让 .ai-tool-approval 整块消失(切骨架屏), * 按钮无 loading 中间态、用户无重审入口;loading 现下沉到 ToolCard 局部 ref,语义正确。 * * path_auth 审批链阶段3b(统一审批模型):**按 tc.kind 分派**两条 IPC—— @@ -46,7 +46,7 @@ const _pendingApprovalIds = new Set() * @param decision path 类专用('once'|'session'|'always'|'deny');risk 类忽略。调用方须据 tc.kind 传对应入参。 */ async function approveToolCall(toolCallId: string, approved: boolean, decision?: 'once' | 'session' | 'always' | 'deny') { - // F-260616-06: 防抖——同一 id 仍在处理中则跳过(竞态点击/网络慢重试) + // 防抖——同一 id 仍在处理中则跳过(竞态点击/网络慢重试) if (_pendingApprovalIds.has(toolCallId)) return _pendingApprovalIds.add(toolCallId) try { @@ -96,7 +96,7 @@ async function approveToolCall(toolCallId: string, approved: boolean, decision?: // IPC 未送达:用户已点过按钮,清看门狗(避免 130s 后误触发假错误)再回滚到失败态 // TD-260621-03 per-conv:清审批 tc 所属会话的 timer(与上方 reset 同 conv,对称成对)。 clearStreamWatchdog(approvalConvId) - state.queue = [] // B-32:IPC 失败即对话结束,清队列防生成中入队消息静默丢失 + state.queue = [] // IPC 失败即对话结束,清队列防生成中入队消息静默丢失 // 仅 IPC 真失败(审批已处理/网络断)走到这里:后端工具失败已改走 emit completed,不进此分支 // 不回滚 pending_approval(会卡死按钮),改设 completed + 错误提示,让用户知晓失败 console.error('[AI] 审批操作未送达后端:', e) diff --git a/src/composables/ai/useAiConversations.ts b/src/composables/ai/useAiConversations.ts index 35dcc0f..366d7a3 100644 --- a/src/composables/ai/useAiConversations.ts +++ b/src/composables/ai/useAiConversations.ts @@ -45,7 +45,7 @@ async function loadConversations() { await switchConversation(pending) } } catch (e) { - // UX-260617-08:网络抖动/IPC 断开时原静默失败,列表可能突空用户不知原因。 + // 网络抖动/IPC 断开时原静默失败,列表可能突空用户不知原因。 // 此处保留旧列表(赋值语句抛出,旧 state.conversations 不被覆盖)+ 推错误气泡反馈。 // 仅首次加载(state.conversations 为空)时推气泡,避免拉取/侧栏刷新重试时反复弹错。 console.error('[AI] 加载会话列表失败:', e) @@ -126,7 +126,7 @@ async function newConversation() { } } -// 切换 token:快速连点 A→B 时,后返回的 A 响应按 token 丢弃,防 messages 错配(FR-R1) +// 切换 token:快速连点 A→B 时,后返回的 A 响应按 token 丢弃,防 messages 错配 let _latestSwitchId = 0 // 最近一次切换的切换窗口缓冲(成功提交路径恢复续显用;失败/过期路径丢弃, // 防陈旧文本在后续切换被误恢复造成内容重复)。由 switchConversation 的 finally 统一填充。 @@ -169,7 +169,7 @@ function parseConvMessages(rawMsgs: any[], fallbackPrefix: string): AiMessage[] } } return rawMsgs - // 过滤 tool 消息 + truncated 软删消息(UX-09:编辑某条 user 后其后消息标 truncated, + // 过滤 tool 消息 + truncated 软删消息编辑某条 user 后其后消息标 truncated, // 保留 DB 可追溯但从视图移除;前端无 status 字段故按原始 JSON 字段过滤) .filter((m: any) => m.role !== 'tool' && m.status !== 'truncated') .map((m: any, i: number) => ({ @@ -177,7 +177,7 @@ function parseConvMessages(rawMsgs: any[], fallbackPrefix: string): AiMessage[] role: m.role, content: m.content || '', model: m.model, - // 用后端持久化的真实时间(BUG-260618-01);老数据无 timestamp 字段回退 Date.now() + // 用后端持久化的真实时间;老数据无 timestamp 字段回退 Date.now() timestamp: typeof m.timestamp === 'number' ? m.timestamp : Date.now(), // 消息级 token 回显:assistant 消息若 DB 持久化了 prompt_tokens/completion_tokens // (V38 迁移后 push_assistant_message 落库的本轮 token),映射回 tokenUsage 供 @@ -350,7 +350,7 @@ export async function switchConversation(id: string, force = false) { state.messages = parseConvMessages(rawMsgs, 'loaded') // parse 成功才置 active(保证 active 与 messages 一致) state.activeConversationId = id - // CSW-P1-4: 切到 round0 进行中的会话时,后端该轮 per_conv 只有 user 消息(assistant 占位 + // 切到 round0 进行中的会话时,后端该轮 per_conv 只有 user 消息(assistant 占位 // 轮末才入),末条是 user → 流式 currentText 无 assistant 承载(MessageList isLastAi 不命中, // flushCurrentText 遇 user break)整轮回复丢失。补前端占位 assistant 气泡承接 currentText, // 生成完成由 AiCompleted/AiAgentRound 收尾 flushCurrentText 回填 content。 @@ -410,7 +410,7 @@ export async function switchConversation(id: string, force = false) { // 恢复该对话积压的待审批:重启后后端从审计表重建了挂起审批, // 此处查回并把对应 toolCard.status 置为待审批,使审批卡片重新可见。 // 按 IPC 返的 kind 渲染:'path' 类显 once/always/deny,'risk' 类显 approve/reject。 - // F2-1(AC-EFF-F2-1):纯文本会话跳过挂起查询(省 1 次 IPC + 后端全扫)——仅当目标会话消息 + // F2-1纯文本会话跳过挂起查询(省 1 次 IPC + 后端全扫)——仅当目标会话消息 // 含工具卡,或目标会话生成中(可能在途产生待审批)才需要恢复挂起。纯文本/无工具历史直接置空。 const hasToolCalls = state.messages.some(m => (m.toolCalls ?? []).length > 0) if (!targetGen && !hasToolCalls) { diff --git a/src/composables/ai/useAiPanel.ts b/src/composables/ai/useAiPanel.ts index b90c495..54a4392 100644 --- a/src/composables/ai/useAiPanel.ts +++ b/src/composables/ai/useAiPanel.ts @@ -19,7 +19,7 @@ import type { AiMessage, AiProviderConfig } from '@/api/types' const appSettings = useAppSettingsStore() -/** 侧栏宽度可拖拽范围(UX-2025-16)。120=窄屏友好,280=宽屏舒适 */ +/** 侧栏宽度可拖拽范围。120=窄屏友好,280=宽屏舒适 */ export const SIDEBAR_WIDTH_MIN = 120 export const SIDEBAR_WIDTH_MAX = 280 export const SIDEBAR_WIDTH_DEFAULT = 200 @@ -44,12 +44,12 @@ function applyUiState(s: any) { if (s.foldedGroups && typeof s.foldedGroups === 'object') { state.foldedGroups = { ...state.foldedGroups, ...s.foldedGroups } } - // 侧栏宽度(UX-2025-16):数值字段,clamp 后写入(与上方 boolean 同级兜底) + // 侧栏宽度数值字段,clamp 后写入(与上方 boolean 同级兜底) state.sidebarWidth = clampSidebarWidth(s.sidebarWidth) } // 启动恢复:从 appSettings(SQLite)读取上次的 UI 布局 + activeProvider(模块级单例 state) -// F-260616-10: activeProvider 恢复对齐 df-ai-active-conv 模式;loadProviders 后还会二次校验 +// activeProvider 恢复对齐 df-ai-active-conv 模式;loadProviders 后还会二次校验 // DB is_default(双保险:localStorage 可能与 DB 不同步时以 DB 为准)。 function restoreUiState() { applyUiState(appSettings.get('df-ai-ui', null)) @@ -79,7 +79,7 @@ export function persistUiState() { sidebarOpen: state.sidebarOpen, archivedCollapsed: state.archivedCollapsed, foldedGroups: state.foldedGroups, - // 侧栏宽度(UX-2025-16):拖拽 mouseup 时由 AiChat 调 persistUiState 落盘 + // 侧栏宽度拖拽 mouseup 时由 AiChat 调 persistUiState 落盘 sidebarWidth: state.sidebarWidth, }) } @@ -141,8 +141,8 @@ function cyclePanelMode() { async function loadProviders() { const list = await aiApi.listProviders() state.providers = list - // F-260616-10: 重启后 activeProvider=null,从 DB is_default 恢复(后端链路正确,仅前端缺同步) - // CR-34-1: 守卫加有效性检查 — savedProvider 指向已删除 provider 时残留无效 id + // 重启后 activeProvider=null,从 DB is_default 恢复(后端链路正确,仅前端缺同步) + // 守卫加有效性检查 — savedProvider 指向已删除 provider 时残留无效 id if (!state.activeProvider || !list.some((p: AiProviderConfig) => p.id === state.activeProvider)) { const def = list.find((p: AiProviderConfig) => p.is_default) if (def) state.activeProvider = def.id @@ -162,7 +162,7 @@ async function loadSkills() { async function setProvider(providerId: string) { await aiApi.setProvider(providerId) state.activeProvider = providerId - // F-260616-10: 持久化到 appSettings,重启后恢复(对齐 df-ai-active-conv 模式) + // 持久化到 appSettings,重启后恢复(对齐 df-ai-active-conv 模式) void appSettings.set('df-ai-active-provider', providerId) } @@ -201,7 +201,7 @@ export function useAiPanel() { loadSkills, setProvider, clearChat, - // UX-2025-16:侧栏宽度拖拽 + // 侧栏宽度拖拽 setSidebarWidth, persistUiState, } diff --git a/src/composables/ai/useAiSend.ts b/src/composables/ai/useAiSend.ts index e8ae048..d0904fb 100644 --- a/src/composables/ai/useAiSend.ts +++ b/src/composables/ai/useAiSend.ts @@ -29,7 +29,7 @@ import { nextMsgId, startApprovalTimer, clearApprovalTimer, clearAllApprovalTime /// 待发送队列上限(超过抛错提示用户) const QUEUE_LIMIT = 10 -/// 排队超时阈值(ms) — 超过后弹 confirm 让用户选择"强制发送"或继续等(B-260616-02) +/// 排队超时阈值(ms) — 超过后弹 confirm 让用户选择"强制发送"或继续等 const QUEUE_TIMEOUT_MS = 30_000 /** @@ -134,7 +134,7 @@ async function doSend(text: string, skill?: string, force = false, parts?: Conte } /** - * 重新生成最后一条 AI 回复(UX-02)。 + * 重新生成最后一条 AI 回复。 * * 与 doSend 的区别: * - 不 push 新 user 消息(触发它的 user 消息已在历史末尾) @@ -190,7 +190,7 @@ async function regenerate() { } /** - * 编辑最后一条 user 消息并重新生成(UX-09)。 + * 编辑最后一条 user 消息并重新生成。 * * 与 regenerate 的区别: * - regenerate:末条须是 assistant,pop 旧 AI 回复后重跑 @@ -497,7 +497,7 @@ async function sendQueuedNow(index: number) { /** 停止当前生成:本地先复位 streaming,再发停止信号。 * 不依赖后端 AiCompleted 收尾——审批态看门狗已 clear(useAiEvents),若 AiCompleted 竞态丢失则 streaming 永久 true 卡死,故本地兜底。 * - * UX-260617-19: stop IPC 失败时的兜底。本地已先置 streaming=false,但若 stop 信号未送达后端, + * stop IPC 失败时的兜底。本地已先置 streaming=false,但若 stop 信号未送达后端, * 后端会继续生成并发回 delta/Completed 事件,与用户已感知的「已停止」语义冲突;更坏情况是 * 后端 hang 既不收尾也不发事件(stop_flag 未置位、无 watchdog 兜底则无恢复路径)。 * 故 stop 失败时不 clearStreamWatchdog,反而重启看门狗:后端若继续生成,活跃 delta 会不断重置它 @@ -551,7 +551,7 @@ export function useAiSend() { } } -// ARC-06: 模块级监听 ai-drain-queue 事件(由 useAiEvents AiCompleted case 发射) +// 模块级监听 ai-drain-queue 事件(由 useAiEvents AiCompleted case 发射) // 打破 useAiEvents ↔ useAiSend 循环依赖:events 不再直接 import 调用 drainQueue let _drainUnlisten: (() => void) | null = null export async function initDrainQueueListener(): Promise { diff --git a/src/composables/ai/useAiWindow.ts b/src/composables/ai/useAiWindow.ts index 9195fb7..5cb59ec 100644 --- a/src/composables/ai/useAiWindow.ts +++ b/src/composables/ai/useAiWindow.ts @@ -53,7 +53,7 @@ async function detachPanel(convId?: string) { return } // 快照当前生成态,供分离窗口接管(保持正在进行的对话) - // F-09:用 per-conv key 写入(多会话各自快照互不覆盖) + // 用 per-conv key 写入(多会话各自快照互不覆盖) // 批4 双轨收口:读 getConvState(enum 真相源)派生,替代旧 generatingConvs.has。 // 桥接语义:has(convId)=true 等价于 conv_state∈{generating,stopping,compressed}(非终止三态)。 if (state.streaming && targetConv) { @@ -67,7 +67,7 @@ async function detachPanel(convId?: string) { // 主窗口 state 与分离窗口 state 独立(各自 webview 独立 JS context, // stores/ai.ts 模块级单例仅在同 webview 内共享),清主窗口 state 不影响分离窗口生成态。 // 分离窗口接管生成后,主窗口不应再持该会话生成态残留(防重开面板时 UI 显生成中幽灵/进度条)。 - // F-09:仅从 convStates 移除该 conv(其他会话可能仍在生成),不全局清空。 + // 仅从 convStates 移除该 conv(其他会话可能仍在生成),不全局清空。 // watchdog 在主窗口 AiChat 卸载(App.vue v-if detached)→ stopListener → clearStreamWatchdog 时已清, // 此处仅清内存 state 视觉残留;localStorage 快照保留供 resumeInDetached 恢复。 setStreaming(false, { convId: targetConv || null, reason: 'detachPanel-clear-main' }) @@ -77,7 +77,7 @@ async function detachPanel(convId?: string) { const win = new WebviewWindow(label, { url, title: 'DevFlow AI', - width: 600, // U-260618: 加宽(原 520 偏窄),与吸附态 AI_DOCK_WIDTH 一致 + width: 600, // 加宽(原 520 偏窄),与吸附态 AI_DOCK_WIDTH 一致 height: 700, minWidth: 360, minHeight: 400, @@ -109,7 +109,7 @@ async function detachPanel(convId?: string) { } /** 关闭分离窗口并回到内嵌面板(主窗口侧调用)。 - * F-09:多会话并发下可能存在多个 detached 窗口,全部关闭并清各自 per-conv 快照。 */ + * 多会话并发下可能存在多个 detached 窗口,全部关闭并清各自 per-conv 快照。 */ async function reattachPanel() { stopFollowMain() const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow') @@ -128,7 +128,7 @@ async function reattachPanel() { // 时序加固:reattach 关闭分离窗口后,显式停分离窗口残留 listener(分离窗口 close 异步, // onBeforeUnmount stopListener 可能延迟),防主面板重新挂载 startListener 时与旧 listener 重叠竞态。 stopListener() - // F-09:清所有 per-conv 快照(分离窗口已全关,快照无消费方) + // 清所有 per-conv 快照(分离窗口已全关,快照无消费方) cleanupAllDetachedSnapshots() } @@ -230,12 +230,12 @@ export async function restoreGeneratingState(opts?: { fromMainPanel?: boolean; c async function resumeInDetached(convId: string | null) { // R5: 不自动 switchConversation —— 分离窗口可看历史列表,但不主动覆盖主窗口 activeConversationId // (避免主窗口期间发消息落错会话)。若需切换会话,由用户在分离窗口显式操作触发。 - // F-09: 透传 convId 给 restoreGeneratingState,定位正确的 per-conv 快照 key。 + // 透传 convId 给 restoreGeneratingState,定位正确的 per-conv 快照 key。 await restoreGeneratingState({ convId: convId || undefined }) } /** 关闭分离窗口自身(分离窗口侧调用)。 - * F-09:从当前 webview 的 label 反推 convId,清自己的 per-conv 快照。 */ + * 从当前 webview 的 label 反推 convId,清自己的 per-conv 快照。 */ async function closeDetachedWindow() { state.docked = false stopFollowMain() @@ -275,7 +275,7 @@ async function dockDetached() { } } -/** 吸附态 AI 窗口宽度/间隙常量(U-260618) */ +/** 吸附态 AI 窗口宽度/间隙常量*/ const AI_DOCK_WIDTH = 600 const AI_DOCK_GAP = 4 // 防 syncToMain 改主窗口 size 触发 onResized 递归回调 diff --git a/src/composables/ai/useStreamRenderer.ts b/src/composables/ai/useStreamRenderer.ts index 44ce04a..929bbb9 100644 --- a/src/composables/ai/useStreamRenderer.ts +++ b/src/composables/ai/useStreamRenderer.ts @@ -26,9 +26,9 @@ export interface StreamBlock { } export interface UseStreamRendererOptions { - /** UX-2025-01:保存当前文字选区(v-html 替换前调) */ + /** 保存当前文字选区(v-html 替换前调) */ saveSelection: () => void - /** UX-2025-01:恢复之前保存的选区(DOM 重建后调) */ + /** 恢复之前保存的选区(DOM 重建后调) */ restoreSelection: () => void /** 跟随意图锁存(跟随中则 rAF 回调内滚到真实底部) */ isFollowingBottom: Ref | ComputedRef @@ -59,7 +59,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { const _blockCache = new Map() const BLOCK_CACHE_LIMIT = 300 // 流式渲染状态:rAF 节流(多 delta 合并一帧,60fps 封顶防主线程阻塞掉帧) - // 分块渲染:已完成块 DOM 稳定不重建 → 选文字保持(UX-2025-01) + // 分块渲染:已完成块 DOM 稳定不重建 → 选文字保持 const streamingBlocks = ref([]) let rafId: number | null = null let lastStreamText = '' @@ -82,7 +82,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { /// (跨双换行不切),非代码段按双换行切段。 /// 流式期末块可能不完整(未闭合围栏/半截段落),交给 parseBlockNoCache 每次重 parse。 /// - /// CR-260615-04:旧实现用正则 /```[^\n]*\n[\s\S]*?(?:```|$)/ 切块,与 marked 围栏规则 + /// 旧实现用正则 /```[^\n]*\n[\s\S]*?(?:```|$)/ 切块,与 marked 围栏规则 /// 不一致——不要求行首、固定 3 反引号,导致行中裸三反引号 / 4+ 反引号嵌套围栏切错, /// 前块缓存固化错误 html。改用 marked.lexer()(经 getMarked() 取 marked 实例)做围栏 /// 感知切块:lexer 把代码围栏识别为单个 type==='code' token(.raw 含整段围栏), @@ -113,7 +113,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { /// 末块不缓存(可能不完整,下次 delta 变),每次重 parse 处理未闭合 token function parseBlockNoCache(block: string): string { // _marked/_purify 在 loadMarkdown 后才就绪;此处仅在 mdReady 后被流式分支调用。 - // UX-260617-25:防御性 null check —— renderStreamingBlocks 顶部已有 mdReady/getMarked/ + // 防御性 null check —— renderStreamingBlocks 顶部已有 mdReady/getMarked/ // getPurify 守卫,正常路径 marked/purify 必非 null。但 marked.use(highlightCode) 注入的 // renderer 在动态 import 后仍可能抛异常(hljs 注册竞态/未知语言)→ 此前直接 marked!.parse() // 会让异常冒泡到 rAF 回调中断流式,表现为生成卡死。这里 null 兜底降级 escapeFallback, @@ -187,7 +187,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { // 流式过程中每 delta 重 parse 会闪烁/错乱)。降级为纯文本(转义围栏原文),AiCompleted // 后整段 renderMd 会做完整代码块渲染(此时围栏已闭合)。已完成块不受影响(围栏已闭合)。 const degrade = isTail && isUnclosedCodeFence(b) - // AC-EFF-S1-1:单块回复病态(整回复无 \n\n/围栏边界,末块=全文)超阈值 → 降级纯文本, + // 单块回复病态(整回复无 \n\n/围栏边界,末块=全文)超阈值 → 降级纯文本, // 防每帧 marked.lexer+parse O(n) 卡顿(流式期间临时纯文本,AiCompleted 后完整 markdown)。 const longSingleBlock = isTail && n === 1 && b.length > SINGLE_BLOCK_TEXT_LIMIT const html = (degrade || longSingleBlock) @@ -226,7 +226,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { if (rafId !== null) return rafId = requestAnimationFrame(() => { rafId = null - // UX-2025-01: 末块 innerHTML 每帧替换,保存选区 + // 末块 innerHTML 每帧替换,保存选区 opts.saveSelection() streamingBlocks.value = renderStreamingBlocks(lastStreamText) nextTick(() => { @@ -245,7 +245,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null } streamingBlocks.value = [] lastStreamText = '' - // AC-EFF-S1-1:清增量基线(防新轮/新会话误命中旧帧已完成块)。 + // 清增量基线(防新轮/新会话误命中旧帧已完成块)。 _prevStreamText = '' _prevCompleted = [] _prevTail = '' @@ -259,7 +259,7 @@ export function useStreamRenderer(opts: UseStreamRendererOptions) { /// 清空实例级块缓存(内容寻址 Map + LRU)。会话切换/开关变化时调用, /// 防旧会话已缓存块被新会话命中(跨会话内容错乱)。仅清缓存,不动 streamingBlocks。 - /// AC-EFF-S1-1:同步清增量基线(已完成块引用旧会话内容,防跨会话误复用)。 + /// 同步清增量基线(已完成块引用旧会话内容,防跨会话误复用)。 function resetStreamCache(): void { _blockCache.clear() _prevStreamText = '' diff --git a/src/composables/ai/useToolApproval.ts b/src/composables/ai/useToolApproval.ts index 696510e..2bd2a0d 100644 --- a/src/composables/ai/useToolApproval.ts +++ b/src/composables/ai/useToolApproval.ts @@ -1,8 +1,8 @@ /** * ToolCard 审批状态机 — 从 ToolCard.vue 抽离的审批 UI 逻辑。 * - * 职责:审批按钮 loading 态、审批挂起时长显示(BUG-260624-02)、 - * 审批超时兜底 toast(UX-260617-14)、approve/reject/path_auth 三选项处理、 + * 职责:审批按钮 loading 态、审批挂起时长显示、 + * 审批超时兜底 toast、approve/reject/path_auth 三选项处理、 * status 变化自动复位 + pending 计时。 * * 不含 store IPC 调用(useAiApproval 负责)和模板渲染——纯状态管理。 @@ -14,14 +14,14 @@ import { aiApi } from '@/api' import type { AiToolCallInfo } from '@/api/types' /** - * 审批按钮 loading 超时兜底(B-260616-08)。 + * 审批按钮 loading 超时兜底。 * 后端回事件使 tc.status 离开 pending_approval → watch 复位。 * 兜底计时到点复位 + toast 提示(后端 IPC 应在 1~3s 内返回;30s 兜底远足超时后允许用户重试)。 * 与 stream 看门狗(130s)解耦:审批是按钮级交互,不是后台流,不需要对齐后端 120s 空闲超时。 */ const APPROVE_LOADING_TIMEOUT_MS = 30_000 -/** AE-2025-05:High 风险工具白名单(后端 tool_registry.rs RiskLevel::High 对齐) */ +/** High 风险工具白名单(后端 tool_registry.rs RiskLevel::High 对齐) */ const HIGH_RISK_TOOLS = new Set([ 'delete_task', 'delete_project', 'restore_project', 'purge_project', 'delete_file', 'run_command', 'http_request', @@ -64,11 +64,11 @@ export function useToolApproval(tc: Ref, emit: ToolApprovalEmit) const { t } = useI18n() const { confirmState, confirmDialog, answerConfirm } = useConfirm() - // ── B-260616-08: 审批 loading 态 ── + // ── 审批 loading 态 ── const approving = ref(false) let approvingTimer: ReturnType | null = null - // ── BUG-260624-02: 审批挂起时长显示 ── + // ── 审批挂起时长显示 ── const pendingSince = ref(null) const waitSecs = ref(0) let waitTimer: ReturnType | null = null @@ -103,7 +103,7 @@ export function useToolApproval(tc: Ref, emit: ToolApprovalEmit) return 'normal' }) - // ── UX-260617-14: 审批超时兜底 toast ── + // ── 审批超时兜底 toast ── const approveToast = reactive({ visible: false, msg: '' }) let _approveToastTimer: ReturnType | null = null @@ -114,7 +114,7 @@ export function useToolApproval(tc: Ref, emit: ToolApprovalEmit) _approveToastTimer = setTimeout(() => { approveToast.visible = false }, 3000) } - /** AE-2025-05:High 风险工具的二次确认文案(按操作类型) */ + /** High 风险工具的二次确认文案(按操作类型) */ function highRiskConfirmMsg(name: string): string { if (name === 'run_command') return t('aiTool.confirmHighExec') if (name === 'http_request') return t('aiTool.confirmHighHttp') @@ -134,7 +134,7 @@ export function useToolApproval(tc: Ref, emit: ToolApprovalEmit) // 先锁防重入(含二次确认 await 期间) approving.value = true try { - // AE-2025-05:High 风险 → 先二次确认 + // High 风险 → 先二次确认 if (HIGH_RISK_TOOLS.has(tc.value.name)) { const ok = await confirmDialog(highRiskConfirmMsg(tc.value.name)) if (!ok) { diff --git a/src/composables/ai/useToolCard.ts b/src/composables/ai/useToolCard.ts index 678556a..730ae70 100644 --- a/src/composables/ai/useToolCard.ts +++ b/src/composables/ai/useToolCard.ts @@ -26,7 +26,7 @@ import type { AiToolCallInfo } from '@/api/types' /** 工具结果 JSON 已知字段(全可选;list_* 运行时返回数组,靠 Array.isArray 区分) */ export interface ToolResult { path?: string - // A1-B5: 只读工具(git status/diff/log)失败路径后端返 {success:false,error} 合法 JSON 对象(非 Err), + // 只读工具(git status/diff/log)失败路径后端返 {success:false,error} 合法 JSON 对象(非 Err), // 前端 isToolFailure 靠此字段判失败(防绿框假成功) success?: boolean error?: string @@ -89,9 +89,9 @@ export interface ToolResult { body?: string body_bytes?: number elapsed_ms?: number - // read_file search 模式(BUG-260625-02):传 search 参数返命中行 matches[{line,content}] 非全文 content + // read_file search 模式传 search 参数返命中行 matches[{line,content}] 非全文 content search?: string - // grep 跨文件内容搜索结果字段(F-260621): + // grep 跨文件内容搜索结果字段 // - output_mode: content(默认)/ files_with_matches / count // - matches: content 模式命中行 [{file,line,content,context?}] // - files: files_with_matches 模式命中文件名数组 @@ -131,9 +131,9 @@ export function shouldKeepOpen(tc: AiToolCallInfo): boolean { } /** - * UX-260616-01: 工具执行失败检测(completed 状态下的失败语义)。 + * 工具执行失败检测(completed 状态下的失败语义)。 * - * 后端 AR-6:Low 风险工具失败不 emit AiError,改 emit AiToolCallCompleted(result=错误), + * 后端 Low 风险工具失败不 emit AiError,改 emit AiToolCallCompleted(result=错误), * 故失败工具 status 仍为 'completed',与成功工具视觉同态(原绿色边框+绿色结果框)。 * 此函数区分三类失败: * - run_command exit_code≠0:结果 JSON 合法,parseResult 命中,看 exit_code 判失败 @@ -164,7 +164,7 @@ export function isToolFailure(tc: AiToolCallInfo): boolean { if (!r) { const raw = typeof tc.result === 'string' ? tc.result : '' if (!raw) return false - // UX-260617-18:收窄为行首锚定(原 /执行失败|failed|error[:\s]/i 匹配任意位置的子串, + // 收窄为行首锚定(原 /执行失败|failed|error[:\s]/i 匹配任意位置的子串, // 误伤 search_files 命中 error.log / run_command stderr 含 "warning" 等正常产出)。 // 仅当某行以「执行失败」(中文 err_msg 前缀)/「Error:」/「Failed:」开头才判失败。 return /^(执行失败|Error:|Failed:)/m.test(raw) @@ -295,7 +295,7 @@ export function formatSizeDiff(diff: number | undefined | null): string { return n >= 0 ? `+${n}` : `${n}` } -/** SW-260618-19: 合并 stdout/stderr(空串跳过),供 cmdOutput 展开 + combineAndTruncateLines 截断共享 DRY */ +/** 合并 stdout/stderr(空串跳过),供 cmdOutput 展开 + combineAndTruncateLines 截断共享 DRY */ export function combineOutputs(stdout: string | undefined, stderr: string | undefined): string { const parts: string[] = [] if (stdout) parts.push(stdout) @@ -346,7 +346,7 @@ export function formatFileInfoResult(r: ToolResult): string { } let base = t('aiTool.fileInfoSize', { path: r.path || '', size: formatBytes(typeof r.size === 'number' ? r.size : 0) }) const extras: string[] = [] - // UX-260618-12: file_info modified 时间(epoch_ms → 客户端 toLocaleString) + // file_info modified 时间(epoch_ms → 客户端 toLocaleString) if (typeof r.modified === 'number' && r.modified > 0) { extras.push(t('aiTool.fileInfoModified', { time: new Date(r.modified).toLocaleString() })) } @@ -365,17 +365,17 @@ export function formatFileInfoResult(r: ToolResult): string { /** * 通用工具结果格式化(非文件类工具)。 - * UX-260616-04:统一 switch 覆盖全部工具产出人类可读摘要,不再走裸 JSON 兜底。 + * 统一 switch 覆盖全部工具产出人类可读摘要,不再走裸 JSON 兜底。 * read_file/list_directory/write_file 有模板特化分支,不进此函数。 * - * P0-3: switch 17 case 拆为策略表 TOOL_FORMATTERS(name → 纯函数),formatToolResult 查表分发。 + * switch 17 case 拆为策略表 TOOL_FORMATTERS(name → 纯函数),formatToolResult 查表分发。 * 新增工具只在此表加一行,零改 formatToolResult 主体。逻辑与原 switch 等价(零行为变更)。 */ // list_* 系列(list_projects/list_tasks/list_ideas/list_trash)共享格式化: // 渲染结构化列表替代裸 JSON,头行按 name 选 i18n key,条目取 name/title/id + status/stack 标注。 function formatListResult(r: ToolResult, name: string): string { - // UX-260618-12: list_* 后端返 {items,total,has_more} 对象(非数组) + // list_* 后端返 {items,total,has_more} 对象(非数组) const items = Array.isArray(r) ? r : (Array.isArray(r.items) ? r.items : []) const total = typeof r.total === 'number' ? r.total : items.length const head = name === 'list_projects' ? t('aiTool.projectCount', { n: total }) @@ -398,8 +398,8 @@ function formatListResult(r: ToolResult, name: string): string { /** advance_task 共享格式化(body + summary 一致): review_rounds>0 显退回累加轮数,否则首推原文案 */ function formatAdvanceTask(r: ToolResult): string { - // UX-260618-09: review_rounds>0 显退回累加轮数,=0 回退原文案(首推无噪音) - // UX-260618-14: 后端返 TaskRecord(含 title),优先显 title,无则回退 id(防裸 UUID) + // review_rounds>0 显退回累加轮数,=0 回退原文案(首推无噪音) + // 后端返 TaskRecord(含 title),优先显 title,无则回退 id(防裸 UUID) return typeof r.review_rounds === 'number' && r.review_rounds > 0 ? t('aiTool.advancedTaskWithRounds', { title: r.title || r.id || '', status: r.status || '', n: r.review_rounds }) : t('aiTool.advancedTask', { title: r.title || r.id || '', status: r.status || '' }) @@ -407,7 +407,7 @@ function formatAdvanceTask(r: ToolResult): string { /** update_task 共享格式化: updated!==false 显字段更新,否则通用 updated */ function formatUpdateTask(r: ToolResult): string { - // UX-260619-04: title 优先(后端补返 title),无则回退 id(防裸 UUID),对齐 advance_task。 + // title 优先(后端补返 title),无则回退 id(防裸 UUID),对齐 advance_task。 return r.updated !== false ? t('aiTool.updatedTaskField', { title: r.title || r.id || '', field: r.field || '' }) : t('aiTool.updated') @@ -444,12 +444,12 @@ function formatSearchFiles(r: ToolResult): string { } /** - * grep 摘要/命中计数标签(F-260621):按 output_mode 取合适文案,三分支: + * grep 摘要/命中计数标签按 output_mode 取合适文案,三分支: * - content(默认):「命中 N 处」(total = 命中行数,total 缺省回退 matches.length) * - files_with_matches:「命中 N 文件」(files.length) * - count:「命中 N 处 / M 文件」(total 行数 + total_files) * - * 抽公共单一函数 G-1:原 ToolResultBody.vue grepHitLabel 与此处 formatGrep 三分支逐行等价, + * 抽公共单一函数 原 ToolResultBody.vue grepHitLabel 与此处 formatGrep 三分支逐行等价, * 现 export 此函数,ToolResultBody.vue 复用,消除文案漂移风险。i18n 走 @/i18n/i18n-helpers 类型安全 t。 */ export function formatGrep(r: ToolResult): string { @@ -489,7 +489,7 @@ function formatBindDir(r: ToolResult): string { /** run_workflow 格式化(body 版):无 execution_id 走 formatJson,否则显完整三要素(task_id/target_status/exec) */ function formatRunWorkflow(r: ToolResult): string { - // UX-260618-05: 后端返 {task_id,target_status,execution_id,status,note},显 execution_id 对应执行实例 + // 后端返 {task_id,target_status,execution_id,status,note},显 execution_id 对应执行实例 const execId = typeof r.execution_id === 'string' && r.execution_id ? r.execution_id : (typeof r.id === 'string' ? r.id : '') @@ -613,10 +613,10 @@ export function formatFetchUrl(r: ToolResult): string { /** * completed 工具结果的一句话摘要(折叠态 header 也能看到,避免光秃秃)。 - * UX-260616-04:覆盖范围与 formatToolResult 对齐(header+body 一致); + * 覆盖范围与 formatToolResult 对齐(header+body 一致); * read_file/list_directory/write_file 有模板特化分支,header 摘要仍显示关键计数。 * - * P0-3: switch 拆为策略表 TOOL_SUMMARIES(name → 纯函数),toolResultSummary 查表分发。 + * switch 拆为策略表 TOOL_SUMMARIES(name → 纯函数),toolResultSummary 查表分发。 * 与 TOOL_FORMATTERS 不共用:summary 版部分 case 输出更短(create/update_project/restore/purge 仅 summary 有; * rename/bind 的 false 分支返空串而非 formatJson;run_workflow 用 workflowTriggeredShort;list_* 多 has_more 判断差异)。 * 共享语义的纯函数(formatAdvanceTask/formatUpdateTask/formatDelete/formatDeleteFile/formatPatchFile/ @@ -669,7 +669,7 @@ function summaryBindDir(r: ToolResult): string { /** run_workflow summary 版:无 execution_id 返空串,有则显 workflowTriggeredShort(body 版显完整三要素) */ function summaryRunWorkflow(r: ToolResult): string { - // UX-260618-05: header 副标题显 execution_id 概要(原 workflowHint「请到工作流页面运行」误导——工作流已触发) + // header 副标题显 execution_id 概要(原 workflowHint「请到工作流页面运行」误导——工作流已触发) const execId = typeof r.execution_id === 'string' && r.execution_id ? r.execution_id : (typeof r.id === 'string' ? r.id : '') diff --git a/src/composables/ai/useToolCardHeader.ts b/src/composables/ai/useToolCardHeader.ts index ee66474..9d5d92e 100644 --- a/src/composables/ai/useToolCardHeader.ts +++ b/src/composables/ai/useToolCardHeader.ts @@ -122,7 +122,7 @@ export function toolDisplayName(tc: AiToolCallInfo): string { */ export function useToolCardHeader(getTc: () => AiToolCallInfo) { /** - * 审批参数值展示(AR-3):对项目类工具的 id/project_id 特化——优先回显项目名,查不到则显示 + * 审批参数值展示对项目类工具的 id/project_id 特化——优先回显项目名,查不到则显示 * 「项目已不存在」提示 + 原 id。非项目工具或其他 key 走通用 formatArgValue。 */ function displayArgValue(arg: { key: string; val: unknown }): string { @@ -136,7 +136,7 @@ export function useToolCardHeader(getTc: () => AiToolCallInfo) { return tGlobal('aiTool.projectIdNotFound', { id }) } } - // UX-260618-14:advance_task/run_workflow 的 task_id → 任务标题 + // advance_task/run_workflow 的 task_id → 任务标题 const taskArgKey = TASK_ID_TOOL_ARG[tc.name] if (taskArgKey && arg.key === taskArgKey) { const id = typeof arg.val === 'string' ? arg.val : '' diff --git a/src/composables/useConfirm.ts b/src/composables/useConfirm.ts index b5b8fb7..cff1c35 100644 --- a/src/composables/useConfirm.ts +++ b/src/composables/useConfirm.ts @@ -32,7 +32,7 @@ export function useConfirm() { function confirmDialog(msg: string, dangerLabel = ''): Promise { return new Promise(resolve => { - // CR-260615-31:并发覆盖前一个 confirmDialog(连点删除/confirm 交错)时, + // 并发覆盖前一个 confirmDialog(连点删除/confirm 交错)时, // 前一个 Promise 永挂(resolve 被覆盖)→ 先 resolve?.(false) 打断前一个视为取消 confirmState.value.resolve?.(false) confirmState.value.msg = msg diff --git a/src/composables/useMarkdown.ts b/src/composables/useMarkdown.ts index 6a7586e..0ed69f1 100644 --- a/src/composables/useMarkdown.ts +++ b/src/composables/useMarkdown.ts @@ -18,7 +18,7 @@ //! 渲染结果不变。 import { computed, ref, type ComputedRef } from 'vue' -// UX-2025-05: 代码块语法高亮 —— 用 highlight.js/lib/core + 按需 registerLanguage, +// 代码块语法高亮 —— 用 highlight.js/lib/core + 按需 registerLanguage, // 只打常用 ~16 门语言进包(避免全量 import ~1MB);core+已注册语言 gzip 后远小于全量。 import hljs from 'highlight.js/lib/core' import javascript from 'highlight.js/lib/languages/javascript' @@ -77,7 +77,7 @@ const mdReady = ref(false) const _mdCache = new Map() /** - * UX-2025-05: marked code renderer 注入 —— 优先按 infostring 指定语言高亮(需已注册), + * marked code renderer 注入 —— 优先按 infostring 指定语言高亮(需已注册), * 否则 highlightAuto 在已注册语言里猜测。输出
,
  * 保留 marked 原 code 块外层结构(pre/code),兼容现有 .ai-md pre code 样式与 DOMPurify。
  *
@@ -114,7 +114,7 @@ function highlightCode(code: string, infostring: string): string {
 async function loadMarkdown() {
   const [{ marked }, dp] = await Promise.all([import('marked'), import('dompurify')])
   marked.setOptions({ breaks: true, gfm: true })
-  // UX-2025-05: 注入 code renderer 做语法高亮(marked.use renderer 合并,仅覆盖 code,
+  // 注入 code renderer 做语法高亮(marked.use renderer 合并,仅覆盖 code,
   // 其余 renderer 沿用 marked 默认)。
   marked.use({
     renderer: {
diff --git a/src/composables/useToast.ts b/src/composables/useToast.ts
index aa0c52e..ff7df57 100644
--- a/src/composables/useToast.ts
+++ b/src/composables/useToast.ts
@@ -14,7 +14,7 @@
 import { reactive } from 'vue'
 import { useTimerOwnership } from './useTimerOwnership'
 
-// P0-2: 加 'success'(保存成功用绿色 toast,此前成功/中性都用 info 致反馈不明确)
+// 加 'success'(保存成功用绿色 toast,此前成功/中性都用 info 致反馈不明确)
 export type ToastType = 'info' | 'error' | 'warning' | 'success'
 
 export interface ToastState {
diff --git a/src/constants/project.ts b/src/constants/project.ts
index abca36b..b3cfbd0 100644
--- a/src/constants/project.ts
+++ b/src/constants/project.ts
@@ -23,7 +23,7 @@ export const PROJECT_STATUS_LABELS: Record = {
 }
 
 /** 项目状态 → 卡片 badge 样式 class */
-// WK-1:对齐 7 态真实生命周期(planning/in_progress/testing/releasing/completed/paused/cancelled)。
+// 对齐 7 态真实生命周期(planning/in_progress/testing/releasing/completed/paused/cancelled)。
 // 类名对齐 ProjectCard.vue scoped 的 stage-* 色系(design/coding/testing/release),paused/cancelled 走新增
 // stage-dim(置灰)。'active' 键删除:后端无此状态,老数据兜底走 stage-design(与 projectStageInfo 兜底一致)。
 export const PROJECT_STATUS_BADGE_CLASS: Record = {
@@ -46,7 +46,7 @@ export interface ProjectStageInfo {
   stage: string
 }
 
-// WK-1:对齐 7 态。stage 值决定 Dashboard dot/chip/fill 颜色档(planning/coding/testing/release/paused/cancelled),
+// 对齐 7 态。stage 值决定 Dashboard dot/chip/fill 颜色档(planning/coding/testing/release/paused/cancelled),
 // 需与 ActiveProjectsPanel.vue 的 dot-*/chip-*/fill-* 类一一对应(paused 为该组件新增 dim 类)。
 // 'active' 键已删,老数据兜底走 projectStageInfo 默认 planning。
 export const PROJECT_STAGE_INFO: Record = {
@@ -68,13 +68,13 @@ export function projectBadgeClass(status: string): string {
 }
 
 export function projectStageInfo(status: string): ProjectStageInfo {
-  // F-09:兜底用字面量而非 PROJECT_STAGE_INFO.planning,避免已删键的隐式依赖
+  // 兜底用字面量而非 PROJECT_STAGE_INFO.planning,避免已删键的隐式依赖
   return PROJECT_STAGE_INFO[status] ?? { stepIndex: 0, progress: 0, stage: 'planning' }
 }
 
 // ── 任务状态 ──
 
-// D-260616-01:前端对齐后端 7 态纯状态机(crates/df-core/src/types.rs TaskStatus)
+// 前端对齐后端 7 态纯状态机(crates/df-core/src/types.rs TaskStatus)
 // 删旧 5 态 Git 工作流导向(review_ready/merged/abandoned)。
 // 后端 task.rs update_task 写 status 经 TaskStatus::is_valid 拦截,旧 5 态无法落库;
 // migrations.rs V1 无 seed,create_task 默认 todo → 新 DB 无旧 5 态数据,安全对齐。
@@ -129,7 +129,7 @@ export function taskStatusClass(status: string): string {
   return TASK_STATUS_CLASS[mapLegacyStatus(status)] ?? 'status-todo'
 }
 
-// WK-8:任务状态机合法流转目标(源状态 → 可达目标态,对齐 TaskDetail.vue ADVANCE_MAP 的 target 集)。
+// 任务状态机合法流转目标(源状态 → 可达目标态,对齐 TaskDetail.vue ADVANCE_MAP 的 target 集)。
 // 列表快捷菜单据此过滤,避免直接置终态/非法跳转(done 为唯一终态,无推进目标;cancelled 可恢复);
 // 新建/手动改状态仍走后端 TaskStatus::is_valid 兜底校验。
 export const TASK_STATUS_TRANSITIONS: Record = {
diff --git a/src/i18n/en/ai.ts b/src/i18n/en/ai.ts
index cb2f0cd..32d5a81 100644
--- a/src/i18n/en/ai.ts
+++ b/src/i18n/en/ai.ts
@@ -23,9 +23,9 @@ export default {
     errorTimeout: 'The response timed out. Please try again.',
     errorNetwork: 'Network connection failed. Please check your network.',
     // CR-30-2 / F-260616-07(d) / decision a1: stream pre-failure retry bubble copy
-    // UX-260616-01: clarify this is an AI response (network/rate-limit) retry, not a tool failure (shown in card)
+    // clarify this is an AI response (network/rate-limit) retry, not a tool failure (shown in card)
     aiStreamRetry: '⚠ AI response failed, retrying ({attempt}/{max})…',
-    // UX-260616-01: Fatal errors (4xx/auth) are not retryable — prompt user to check settings instead
+    // Fatal errors (4xx/auth) are not retryable — prompt user to check settings instead
     notRetryable: 'This error cannot be resolved by retrying. Please check the configuration.',
     streamInterruptedAfterTool: '⚠ The tool finished, but the following reply was interrupted (no data stream for a long time). You can click Continue to retry.',
     streamInterrupted: '⚠ The response was interrupted (no data stream for a long time). This may be due to a network disconnection or a backend error. Please try again.',
@@ -38,7 +38,7 @@ export default {
     approvalNotDelivered: 'Approval operation failed to reach the backend: {error}',
     queuedSendFailed: 'Failed to send queued message: {error}',
     approvalTimeout: '⚰ Approval timed out, automatically rejected: {toolName}',
-    // UX-260617-08: conversation load/switch failure feedback (network jitter/IPC down → empty list/blank, was silent)
+    // conversation load/switch failure feedback (network jitter/IPC down → empty list/blank, was silent)
     loadConvFail: 'Failed to load conversations. Please check your network or backend connection.',
     switchConvFail: 'Failed to switch conversation. Please retry.',
     // M30: rename/archive/pin IPC failure feedback (was no try/catch, exceptions leaked to unhandledrejection)
@@ -52,7 +52,7 @@ export default {
     unarchiveAction: 'unarchive',
     pinAction: 'pin',
     unpinAction: 'unpin',
-    // AE-2025-07: Agentic loop progress bar copy
+    // Agentic loop progress bar copy
     // Note: max round / completed tool count are not surfaced by backend this round;
     // template conditionally omits them. Add *WithMax keys once backend exposes them.
     agenticProgress: '🔄 Round {round} · ⏳{pending} pending',
diff --git a/src/i18n/en/aiChat.ts b/src/i18n/en/aiChat.ts
index c44a0af..8d513c8 100644
--- a/src/i18n/en/aiChat.ts
+++ b/src/i18n/en/aiChat.ts
@@ -71,7 +71,7 @@ export default {
     selectModel: 'Select model',
     noModels: 'No models available. Please fetch in settings.',
 
-    // ── Empty states (UX-2025-20: example prompt cards + no-provider guidance) ──
+    // ── Empty states example prompt cards + no-provider guidance) ──
     emptyNoProvider: 'No AI provider configured',
     emptyNoProviderHint: 'Please add a provider in settings to start chatting',
     emptyTitle: 'Chat with AI assistant',
@@ -84,7 +84,7 @@ export default {
     goToSettings: 'Open settings',
     goToSettingsHint: 'The detached window cannot open settings directly. Configure AI providers on the main window’s Settings page.',
 
-    // ── Provider switch feedback (UX-2025-14) ──
+    // ── Provider switch feedback ──
     providerSwitched: 'Switched to {model}',
     providerSwitchHint: 'Current model: {model}',
 
@@ -107,7 +107,7 @@ export default {
     skillNoMatch: 'No matching skill',
     skillNotLoaded: 'No local skills loaded',
 
-    // ── UX-10: @ entity mention (@ opens popover, select inserts [Type:Name] token) ──
+    // ── @ entity mention (@ opens popover, select inserts [Type:Name] token) ──
     mentionHint: "{'@'} to mention project/task",
     mentionGroupProject: 'Projects',
     mentionGroupTask: 'Tasks',
@@ -136,13 +136,13 @@ export default {
 
     // ── Confirm dialog ──
     confirmDeleteConversation: 'Delete chat "{title}"? This cannot be undone.',
-    // UX-260617-24: stronger warning when deleting the currently active conversation
+    // stronger warning when deleting the currently active conversation
     confirmDeleteActiveConversation: '"{title}" is the chat you are currently viewing. Deleting it will clear the current view and cannot be undone. Continue?',
     clearChat: 'Clear',
     confirmClearChat: 'Are you sure you want to clear all messages in this chat? This cannot be undone.',
     toastSendFail: 'Send failed: {msg}',
 
-    // ── Message copy (F-260615-13) ──
+    // ── Message copy ──
     copyMsg: 'Copy message',
     copied: 'Copied',
     copyFailed: 'Copy failed',
@@ -151,14 +151,14 @@ export default {
     copiedAsImage: 'Copied as image',
     copyAsImageFailed: 'Failed to copy as image',
 
-    // ── Message actions bar (UX-02) ──
+    // ── Message actions bar ──
     regenerate: 'Regenerate',
     regenerateFailed: 'Regenerate failed: {msg}',
     regenerateUnavailable: 'No reply to regenerate',
     // fetch_url "Install obscura via AI" button triggers sendMessage; failure toast (mirrors editMessageFailed)
     sendFailed: 'Send failed: {msg}',
 
-    // ── UX-09: edit last user message and regenerate ──
+    // ── edit last user message and regenerate ──
     editMessage: 'Edit',
     editMessageFailed: 'Edit failed: {msg}',
     editNotLastUser: 'Only the last user message can be edited',
@@ -168,7 +168,7 @@ export default {
     retry: 'Retry',
     goToSettingsAction: 'Open settings',
 
-    // ── F-260616-03: max iterations reached pause action card ──
+    // ── max iterations reached pause action card ──
     maxRoundsReached: 'Max rounds reached. Continue?',
     maxRoundsHint: 'Continue runs another full round; stop ends with current results',
     continueLoop: 'Continue',
diff --git a/src/i18n/en/aiTool.ts b/src/i18n/en/aiTool.ts
index 833d723..3a913f8 100644
--- a/src/i18n/en/aiTool.ts
+++ b/src/i18n/en/aiTool.ts
@@ -6,11 +6,11 @@ export default {
     approveAll: 'Approve All ({n})',
     rejectAll: 'Reject All',
     rejectedHint: 'User rejected this action',
-    // B-260616-12: tool execution slow hint (frontend fallback, no backend cancel IPC)
+    // tool execution slow hint (frontend fallback, no backend cancel IPC)
     executionSlow: 'Tool "{name}" is taking longer than expected, please wait…',
-    // BUG-260624-02: approval pending duration hint (>2min orange, >5min red)
+    // approval pending duration hint (>2min orange, >5min red)
     waitTime: '⏱ Waiting {time}',
-    // UX-260616-01: tool execution failure marker (AR-6 keeps status=completed, red to distinguish from success)
+    // tool execution failure marker (AR-6 keeps status=completed, red to distinguish from success)
     executionFailed: 'Failed',
     executionFailedHint: 'Tool execution failed, see details below',
     // Singular expand/collapse (ToolCard single-card content, command output toggle; expandAll/collapseAll are for ToolCardList global multi-group)
@@ -31,7 +31,7 @@ export default {
     dirFallback: 'List Directory',
     writePrefix: 'Write',
     writeFallback: 'Write File',
-    // grep cross-file content search header (F-260621)
+    // grep cross-file content search header 
     grepPrefix: 'Search',
     grepFallback: 'Content Search',
     // CRUD tool display names (delete/restore/purge/update_project resolve project name by id; fallback when no id or unresolved)
@@ -63,26 +63,26 @@ export default {
     purged: 'Purged',
     purgeIneffective: 'Purge ineffective',
     workflowHint: 'Run it on the workflow page',
-    // UX-260618-05: run_workflow tool_result shows execution_id (workflowHint misleading — workflow already triggered; old key kept for other refs)
+    // run_workflow tool_result shows execution_id (workflowHint misleading — workflow already triggered; old key kept for other refs)
     workflowTriggered: 'Task {id} triggered workflow (advancing to {status}, execution {exec})',
     workflowTriggeredShort: 'Execution {exec}',
 
-    // UX-260616-04: formatToolResult covers all tools with human-readable summaries (missing keys)
+    // formatToolResult covers all tools with human-readable summaries (missing keys)
     updatedTaskField: 'Task「{title}」 updated field {field}',
     advancedTask: 'Task「{title}」 advanced to {status}',
-    // UX-260618-09: advance_task return-round accumulation (shown only when >0); UX-260618-14: {title} = task name (TaskRecord), fallback id
+    // advance_task return-round accumulation (shown only when >0); {title} = task name (TaskRecord), fallback id
     advancedTaskWithRounds: 'Task「{title}」 advanced to {status} (returned {n} times)',
     commandOk: 'Command succeeded ({ms} ms)',
     commandFailed: 'Command failed (exit code {code})',
     commandOutputLines: '{n} lines of output',
     patchedFile: 'Patched {path} ({diff} bytes)',
-    // UX-260618-06: patch_file multi-match hint + size_diff unit
+    // patch_file multi-match hint + size_diff unit
     patchedMatches: 'matched {n} places',
     bytesUnit: 'bytes',
     patchedNoChange: '{path} no change',
     appendedTo: 'Appended {bytes} to {path}',
     renamedFile: 'Renamed {from} → {to}',
-    // UX-260618-12: rename_file bytes/cross-volume + write_file overwrite/encoding hints
+    // rename_file bytes/cross-volume + write_file overwrite/encoding hints
     renamedWithBytes: 'Renamed {from} → {to} ({bytes})',
     renamedCrossVolume: 'Moved {from} → {to} (cross-volume, {bytes})',
     writeOverwrite: 'overwrote {old} → {neww}',
@@ -113,7 +113,7 @@ export default {
     fetchUrlFallback: 'Fetch URL',
     installObscura: 'Install obscura via AI',
     obscuraInstallPrompt: 'Please install obscura (a Rust headless browser, used by fetch_url render=true for JS rendering). Try `cargo install obscura` first; if cargo is unavailable, download the prebuilt binary for this platform from the GitHub releases (github.com/h4ckf0r0day/obscura) and place it on PATH. After installing, verify with `obscura --version` and tell me I can retry the web fetch.',
-    // grep cross-file content search results (F-260621): per output_mode phrasing
+    // grep cross-file content search results per output_mode phrasing
     grepHitsN: '{n} hits',
     grepFilesN: '{n} files',
     grepCountSummary: '{n} hits / {files} files',
@@ -121,10 +121,10 @@ export default {
     grepTruncated: 'Results truncated (max reached), narrow pattern or add glob',
     boundDir: 'Bound directory "{path}" stack: {stack}',
     deleteFilePermanent: 'Permanently deleted {path}',
-    // UX-260618-07: soft-delete adds backup_path (trash filename so user knows where to recover from)
+    // soft-delete adds backup_path (trash filename so user knows where to recover from)
     deleteFileSoft: 'Soft-deleted {path} (backed up, recover from: {backup_path})',
     fileInfoSize: '{path} {size}',
-    // UX-260618-12: file_info modified time (epoch_ms → client toLocaleString)
+    // file_info modified time (epoch_ms → client toLocaleString)
     fileInfoModified: ' · modified {time}',
     fileInfoLines: ' · {n} lines',
     fileInfoBinary: ' · binary',
@@ -133,11 +133,11 @@ export default {
 
     projectLabel: '"{name}"',
     projectIdNotFound: '(project not found, id={id})',
-    // UX-260618-14: advance_task/run_workflow task_id → task title (store.tasks lookup; fallback id)
+    // advance_task/run_workflow task_id → task title (store.tasks lookup; fallback id)
     taskLabel: '"{name}"',
     taskIdNotFound: '(task not in current list, id={id})',
 
-    // AE-2025-05: High-risk approval second confirm (ask once more before approve; misclick cancellable)
+    // High-risk approval second confirm (ask once more before approve; misclick cancellable)
     confirmHighDelete: 'Permanently delete? This action cannot be undone',
     confirmHighExec: 'Run this command?',
     confirmHighGeneric: 'Proceed with this high-risk action?',
@@ -145,7 +145,7 @@ export default {
     // Tool result detail format (plain-text result + approval placeholder appends arg summary)
     resultDetail: '{raw} ({detail})',
 
-    // UX-260617-17: ToolCardList group header (shown when 2+ same-name tools; was hardcoded zh)
+    // ToolCardList group header (shown when 2+ same-name tools; was hardcoded zh)
     toolGroup: {
       readFile: 'Read File',
       writeFile: 'Write File',
@@ -165,10 +165,10 @@ export default {
       httpRequest: 'HTTP Request',
     },
 
-    // UX-260617-14: approval loading fallback timeout notice (no backend ack, card still pending_approval → reset + prompt)
+    // approval loading fallback timeout notice (no backend ack, card still pending_approval → reset + prompt)
     approveLoadingTimeout: 'Approval response timed out, please retry or check the backend',
 
-    // UX-260617-18: tool failure detection copy (regex narrowed to line-anchored err_msg prefix)
+    // tool failure detection copy (regex narrowed to line-anchored err_msg prefix)
     // (reuse executionFailed/executionFailedHint, no new key)
 
     // http_request High-risk confirm: external network call, data may leak via URL/body
diff --git a/src/i18n/en/auditLog.ts b/src/i18n/en/auditLog.ts
index 44156e2..afca14b 100644
--- a/src/i18n/en/auditLog.ts
+++ b/src/i18n/en/auditLog.ts
@@ -49,7 +49,7 @@ export default {
     decided: {
       human: 'Human',
       auto: 'Auto',
-      // SA-1: cover all backend decided_by sources (different decision paths, more precise than generic 'auto')
+      // cover all backend decided_by sources (different decision paths, more precise than generic 'auto')
       auto_trust: 'Auto (trusted)',
       auto_dedup: 'Auto (deduped)',
       cache_hit: 'Auto (cached)',
@@ -66,7 +66,7 @@ export default {
     convTruncated: 'Content truncated (expand to view full conversation)',
     convSelectHint: '← Select a conversation to view full interaction log',
     convReasoning: 'Reasoning (thinking)',
-    // SA-2: conversation role labels (previously hardcoded Chinese, now i18n)
+    // conversation role labels (previously hardcoded Chinese, now i18n)
     convRoleUser: '🧑 User',
     convRoleAssistant: '🤖 Assistant',
     convRoleTool: '🔧 Tool',
diff --git a/src/i18n/en/settings.ts b/src/i18n/en/settings.ts
index d5a10d4..d7c2793 100644
--- a/src/i18n/en/settings.ts
+++ b/src/i18n/en/settings.ts
@@ -230,7 +230,7 @@ export default {
     confirmDiscardUnsaved: 'The form has unsaved changes. Discard them?',
     toastSaved: 'Saved',
     toastSaveFail: 'Save failed: {msg}',
-    // P0-2: save-failure classified guidance (persistent banner, not raw Err text)
+    // save-failure classified guidance (persistent banner, not raw Err text)
     saveErrKeyringTitle: 'Failed to write key to the OS keychain',
     saveErrKeyringDetail: 'Original config left unchanged. Backend reason: {msg}\nSuggestion: check the OS keychain/credential store permissions (Windows Credential Manager / macOS Keychain / Linux secret-service daemon), then click "Save" again to retry.',
     saveErrGenericTitle: 'Failed to save provider config',
diff --git a/src/i18n/en/tasks.ts b/src/i18n/en/tasks.ts
index 65655cd..df8f12d 100644
--- a/src/i18n/en/tasks.ts
+++ b/src/i18n/en/tasks.ts
@@ -95,7 +95,7 @@ export default {
       desc: 'Description',
       branch: 'Branch',
       priority: 'Priority',
-      // F-260619-01: related idea (1:1, optional)
+      // related idea (1:1, optional)
       idea: 'Related Idea',
       ideaPlaceholder: 'None (optional)',
       titlePlaceholder: 'Enter task title...',
diff --git a/src/i18n/zh-CN/ai.ts b/src/i18n/zh-CN/ai.ts
index da1b4c1..b664558 100644
--- a/src/i18n/zh-CN/ai.ts
+++ b/src/i18n/zh-CN/ai.ts
@@ -23,9 +23,9 @@ export default {
     errorTimeout: '响应超时,请重试',
     errorNetwork: '网络连接失败,请检查网络',
     // CR-30-2 / F-260616-07(d) / 决策 a1: 流前失败重试中错误气泡内更新文案
-    // UX-260616-01: 明确这是 AI 响应(网络/限流)重试,非工具执行失败(工具失败在卡片内显示)
+    // 明确这是 AI 响应(网络/限流)重试,非工具执行失败(工具失败在卡片内显示)
     aiStreamRetry: '⚠ AI 响应失败,正在重试({attempt}/{max})…',
-    // UX-260616-01: Fatal 类错误(4xx/鉴权)不可重试,提示用户去设置而非重试
+    // Fatal 类错误(4xx/鉴权)不可重试,提示用户去设置而非重试
     notRetryable: '此错误无法通过重试解决,请检查配置',
     streamInterruptedAfterTool: '⚠ 工具已执行完成,后续回复中断(长时间无数据流)。可点继续重试。',
     streamInterrupted: '⚠ 响应中断(长时间无数据流)。可能是网络断连或后端异常,请重试。',
@@ -38,7 +38,7 @@ export default {
     approvalNotDelivered: '审批操作未送达后端:{error}',
     queuedSendFailed: '队列消息发送失败:{error}',
     approvalTimeout: '⚰ 审批超时,已自动拒绝:{toolName}',
-    // UX-260617-08:对话加载/切换失败反馈(网络抖动/IPC 断开 → 列表空/切换空白,原静默无反馈)
+    // 对话加载/切换失败反馈(网络抖动/IPC 断开 → 列表空/切换空白,原静默无反馈)
     loadConvFail: '对话列表加载失败,请检查网络或后端连接',
     switchConvFail: '对话切换失败,请重试',
     // M30:重命名/归档/置顶 IPC 失败反馈(原无 try/catch 异常进 unhandledrejection,用户无感)
@@ -52,7 +52,7 @@ export default {
     unarchiveAction: '取消归档',
     pinAction: '置顶',
     unpinAction: '取消置顶',
-    // AE-2025-07: Agentic 循环进度条文案
+    // Agentic 循环进度条文案
     // 注:max 轮次/已完成工具数本轮后端暂不透传,模板条件渲染省略;后端补全后新增 *WithMax key
     agenticProgress: '🔄 循环 {round} · ⏳{pending}待审批',
     agenticProgressNoPending: '🔄 循环 {round} · ✅{completed}个工具完成',
diff --git a/src/i18n/zh-CN/aiChat.ts b/src/i18n/zh-CN/aiChat.ts
index 9d1d580..e1a598c 100644
--- a/src/i18n/zh-CN/aiChat.ts
+++ b/src/i18n/zh-CN/aiChat.ts
@@ -71,7 +71,7 @@ export default {
     selectModel: '选择模型',
     noModels: '无可用模型,请在设置中拉取',
 
-    // ── 空状态(UX-2025-20:示例问题卡片 + 无 provider 引导) ──
+    // ── 空状态示例问题卡片 + 无 provider 引导) ──
     emptyNoProvider: '未配置 AI 提供商',
     emptyNoProviderHint: '请在设置中添加 Provider 后开始对话',
     emptyTitle: '与 AI 助手对话',
@@ -84,7 +84,7 @@ export default {
     goToSettings: '去设置',
     goToSettingsHint: '分离窗口无法直接打开设置,请在主窗口的设置页配置 AI 提供商',
 
-    // ── Provider 切换反馈(UX-2025-14) ──
+    // ── Provider 切换反馈──
     providerSwitched: '已切换到 {model}',
     providerSwitchHint: '当前模型:{model}',
 
@@ -107,7 +107,7 @@ export default {
     skillNoMatch: '无匹配技能',
     skillNotLoaded: '未读取到本机技能',
 
-    // ── UX-10: @ 实体引用(@ 触发 popover,选中插入 [类型:名] 标记) ──
+    // ── @ 实体引用(@ 触发 popover,选中插入 [类型:名] 标记) ──
     mentionHint: "{'@'} 引用项目/任务",
     mentionGroupProject: '项目',
     mentionGroupTask: '任务',
@@ -137,13 +137,13 @@ export default {
 
     // ── 确认弹层 ──
     confirmDeleteConversation: '确定删除对话「{title}」?该操作不可恢复。',
-    // UX-260617-24:删除当前活跃对话的醒目提示(活跃对话误删会清空当前视图,影响更大)
+    // 删除当前活跃对话的醒目提示(活跃对话误删会清空当前视图,影响更大)
     confirmDeleteActiveConversation: '「{title}」是当前正在查看的对话,删除后将清空当前视图且不可恢复,确定继续?',
     clearChat: '清空',
     confirmClearChat: '确定清空当前对话的所有消息?此操作不可恢复。',
     toastSendFail: '发送失败:{msg}',
 
-    // ── 消息拷贝(F-260615-13) ──
+    // ── 消息拷贝──
     copyMsg: '复制消息',
     copied: '已复制',
     copyFailed: '复制失败',
@@ -152,14 +152,14 @@ export default {
     copiedAsImage: '已复制为图片',
     copyAsImageFailed: '复制为图片失败',
 
-    // ── 消息操作栏(UX-02) ──
+    // ── 消息操作栏──
     regenerate: '重新生成',
     regenerateFailed: '重新生成失败:{msg}',
     regenerateUnavailable: '没有可重新生成的回复',
     // fetch_url "AI 帮装 obscura" 按钮触发 sendMessage 失败的 toast(对齐 editMessageFailed 模式)
     sendFailed: '发送失败:{msg}',
 
-    // ── UX-09: 编辑末条 user 消息并重新生成 ──
+    // ── 编辑末条 user 消息并重新生成 ──
     editMessage: '编辑',
     editMessageFailed: '编辑失败:{msg}',
     editNotLastUser: '只能编辑最后一条用户消息',
@@ -169,7 +169,7 @@ export default {
     retry: '重试',
     goToSettingsAction: '去设置',
 
-    // ── F-260616-03: 达 max_iterations 暂停态操作卡 ──
+    // ── 达 max_iterations 暂停态操作卡 ──
     maxRoundsReached: '已达最大轮次,是否继续?',
     maxRoundsHint: '继续将再跑一轮上限;停止则按当前结果收尾',
     continueLoop: '继续',
diff --git a/src/i18n/zh-CN/aiTool.ts b/src/i18n/zh-CN/aiTool.ts
index 8f842b7..fe20908 100644
--- a/src/i18n/zh-CN/aiTool.ts
+++ b/src/i18n/zh-CN/aiTool.ts
@@ -6,11 +6,11 @@ export default {
     approveAll: '全部批准 {n} 项',
     rejectAll: '全部拒绝',
     rejectedHint: '用户拒绝了此操作',
-    // B-260616-12:工具执行超时提示(前端降级,无后端取消 IPC)
+    // 工具执行超时提示(前端降级,无后端取消 IPC)
     executionSlow: '工具「{name}」执行较久,请稍候…',
-    // BUG-260624-02:审批挂起时长提示(>2分钟橙色,>5分钟红色)
+    // 审批挂起时长提示(>2分钟橙色,>5分钟红色)
     waitTime: '⏱ 已等待 {time}',
-    // UX-260616-01:工具执行失败视觉标识(AR-6 下 status=completed,需红色区分于成功)
+    // 工具执行失败视觉标识(AR-6 下 status=completed,需红色区分于成功)
     executionFailed: '执行失败',
     executionFailedHint: '工具执行失败,详情见下方',
     collapse: '收起',
@@ -31,7 +31,7 @@ export default {
     dirFallback: '列出目录',
     writePrefix: '写入',
     writeFallback: '写入文件',
-    // grep 跨文件内容搜索头部(F-260621)
+    // grep 跨文件内容搜索头部
     grepPrefix: '搜索',
     grepFallback: '内容搜索',
     // CRUD 类工具显示名(delete/restore/purge/update_project 按 id 解析项目名;无 id 或解析失败走 fallback)
@@ -64,26 +64,26 @@ export default {
     purged: '已彻底删除',
     purgeIneffective: '清除未生效',
     workflowHint: '请到工作流页面运行',
-    // UX-260618-05:run_workflow tool_result 显 execution_id(workflowHint 误导——工作流已触发;旧 key 保留防他处引用)
+    // run_workflow tool_result 显 execution_id(workflowHint 误导——工作流已触发;旧 key 保留防他处引用)
     workflowTriggered: '任务 {id} 已触发工作流(推进至 {status},执行实例 {exec})',
     workflowTriggeredShort: '执行实例 {exec}',
 
-    // UX-260616-04:formatToolResult 覆盖全部工具产出人类可读摘要(补缺失 key)
+    // formatToolResult 覆盖全部工具产出人类可读摘要(补缺失 key)
     updatedTaskField: '任务「{title}」已更新字段 {field}',
     advancedTask: '任务「{title}」已推进至 {status}',
-    // UX-260618-09:advance_task 退回累加轮数(>0 才显);UX-260618-14:{title} 显任务名(后端返 TaskRecord),回退 id
+    // advance_task 退回累加轮数(>0 才显);{title} 显任务名(后端返 TaskRecord),回退 id
     advancedTaskWithRounds: '任务「{title}」已推进至 {status}(已退回 {n} 次)',
     commandOk: '命令成功({ms} 毫秒)',
     commandFailed: '命令失败(退出码 {code})',
     commandOutputLines: '{n} 行输出',
     patchedFile: '已修改 {path}(增减 {diff} 字节)',
-    // UX-260618-06:patch_file 多匹配提示 + size_diff 单位
+    // patch_file 多匹配提示 + size_diff 单位
     patchedMatches: '匹配 {n} 处',
     bytesUnit: '字节',
     patchedNoChange: '{path} 无更改',
     appendedTo: '已向 {path} 追加 {bytes}',
     renamedFile: '已重命名 {from} → {to}',
-    // UX-260618-12:rename_file 字节/跨卷 + write_file 覆盖/编码提示
+    // rename_file 字节/跨卷 + write_file 覆盖/编码提示
     renamedWithBytes: '已重命名 {from} → {to}({bytes})',
     renamedCrossVolume: '已移动 {from} → {to}(跨卷,{bytes})',
     writeOverwrite: '覆盖 {old} → {neww}',
@@ -114,7 +114,7 @@ export default {
     fetchUrlFallback: '获取网页',
     installObscura: 'AI 帮装 obscura',
     obscuraInstallPrompt: '请帮我安装 obscura(Rust 无头浏览器,用于 fetch_url 的 render=true JS 渲染)。优先尝试 `cargo install obscura`,若 cargo 不可用则从 GitHub releases(github.com/h4ckf0r0day/obscura)下载对应平台预编译二进制并放入 PATH。安装完成后用 `obscura --version` 验证,然后告诉我可以重试网页抓取了。',
-    // grep 跨文件内容搜索结果(F-260621):按 output_mode 分文案
+    // grep 跨文件内容搜索结果按 output_mode 分文案
     grepHitsN: '命中 {n} 处',
     grepFilesN: '命中 {n} 文件',
     grepCountSummary: '命中 {n} 处 / {files} 文件',
@@ -122,10 +122,10 @@ export default {
     grepTruncated: '结果已截断(达上限),收紧 pattern 或加 glob',
     boundDir: '已绑定目录「{path}」技术栈:{stack}',
     deleteFilePermanent: '已硬删除 {path}',
-    // UX-260618-07:软删补 backup_path(回收站文件名,用户知从哪恢复)
+    // 软删补 backup_path(回收站文件名,用户知从哪恢复)
     deleteFileSoft: '已软删除 {path}(已备份,可从 {backup_path} 恢复)',
     fileInfoSize: '{path} {size}',
-    // UX-260618-12:file_info 修改时间(epoch_ms → 客户端 toLocaleString)
+    // file_info 修改时间(epoch_ms → 客户端 toLocaleString)
     fileInfoModified: ' · 修改于 {time}',
     fileInfoLines: ' · {n} 行',
     fileInfoBinary: ' · 二进制',
@@ -135,11 +135,11 @@ export default {
     // 审批参数特化(id/project_id 回显项目名,查不到标注原 id)
     projectLabel: '「{name}」',
     projectIdNotFound: '(项目已不存在, id={id})',
-    // UX-260618-14:advance_task/run_workflow 的 task_id 回显任务标题(查 store.tasks,无则标 id)
+    // advance_task/run_workflow 的 task_id 回显任务标题(查 store.tasks,无则标 id)
     taskLabel: '「{name}」',
     taskIdNotFound: '(任务不在当前列表, id={id})',
 
-    // AE-2025-05:High 风险审批二次确认文案(批准前再问一次,误点可取消)
+    // High 风险审批二次确认文案(批准前再问一次,误点可取消)
     confirmHighDelete: '确定永久删除?此操作不可恢复',
     confirmHighExec: '确定执行此命令?',
     confirmHighGeneric: '确定执行此高危操作?',
@@ -149,7 +149,7 @@ export default {
     // 工具结果详情格式(非 JSON 纯文本 + 审批占位时补参数摘要)
     resultDetail: '{raw}({detail})',
 
-    // UX-260617-17:ToolCardList 分组标题(同名工具 >=2 时显示;原硬编码中文,英文化)
+    // ToolCardList 分组标题(同名工具 >=2 时显示;原硬编码中文,英文化)
     toolGroup: {
       readFile: '读取文件',
       writeFile: '写入文件',
@@ -169,10 +169,10 @@ export default {
       httpRequest: 'HTTP 请求',
     },
 
-    // UX-260617-14:审批 loading 超时兜底提示(后端无回执,卡片仍 pending_approval 时复位 + 提示用户)
+    // 审批 loading 超时兜底提示(后端无回执,卡片仍 pending_approval 时复位 + 提示用户)
     approveLoadingTimeout: '审批响应超时,请重试或检查后端',
 
-    // UX-260617-18:工具失败检测文案(收窄正则后,行首锚定 err_msg 前缀)
+    // 工具失败检测文案(收窄正则后,行首锚定 err_msg 前缀)
     // (复用 executionFailed/executionFailedHint,不新增)
 
     // 审批参数键标签
diff --git a/src/i18n/zh-CN/auditLog.ts b/src/i18n/zh-CN/auditLog.ts
index d41892b..9dbf735 100644
--- a/src/i18n/zh-CN/auditLog.ts
+++ b/src/i18n/zh-CN/auditLog.ts
@@ -49,7 +49,7 @@ export default {
     decided: {
       human: '人工',
       auto: '自动',
-      // SA-1:补齐后端 decided_by 全部来源(审计记录不同决策路径,精确区分而非笼统 'auto')
+      // 补齐后端 decided_by 全部来源(审计记录不同决策路径,精确区分而非笼统 'auto')
       auto_trust: '自动(信任)',
       auto_dedup: '自动(去重)',
       cache_hit: '自动(缓存)',
@@ -66,7 +66,7 @@ export default {
     convTruncated: '内容过长已截断(展开查看完整对话)',
     convSelectHint: '← 选择左侧一次对话查看完整交互日志',
     convReasoning: '推理过程(thinking)',
-    // SA-2:对话角色标签(原硬编码 '🧑 用户' 等,现 i18n 化)
+    // 对话角色标签(原硬编码 '🧑 用户' 等,现 i18n 化)
     convRoleUser: '🧑 用户',
     convRoleAssistant: '🤖 助手',
     convRoleTool: '🔧 工具',
diff --git a/src/i18n/zh-CN/settings.ts b/src/i18n/zh-CN/settings.ts
index cb1ce75..2fced79 100644
--- a/src/i18n/zh-CN/settings.ts
+++ b/src/i18n/zh-CN/settings.ts
@@ -229,7 +229,7 @@ export default {
     confirmDiscardUnsaved: '表单有未保存的改动,确定放弃吗?',
     toastSaved: '已保存',
     toastSaveFail: '保存失败:{msg}',
-    // P0-2: 保存失败分类建议(常驻错误横幅,非裸 Err 文本)
+    // 保存失败分类建议(常驻错误横幅,非裸 Err 文本)
     saveErrKeyringTitle: '密钥写入系统钥匙串失败',
     saveErrKeyringDetail: '已保留原配置未改动。后端原因:{msg}\n建议:检查操作系统钥匙串/凭据管理器权限(Windows 凭据管理器 / macOS 钥匙串 / Linux secret-service 守护进程),确认后再次点击「保存」重试。',
     saveErrGenericTitle: '保存提供商配置失败',
diff --git a/src/i18n/zh-CN/tasks.ts b/src/i18n/zh-CN/tasks.ts
index ee98794..ea137e6 100644
--- a/src/i18n/zh-CN/tasks.ts
+++ b/src/i18n/zh-CN/tasks.ts
@@ -95,7 +95,7 @@ export default {
       desc: '描述',
       branch: '分支',
       priority: '优先级',
-      // F-260619-01:关联灵感(1对1 单向,可选)
+      // 关联灵感(1对1 单向,可选)
       idea: '关联灵感',
       ideaPlaceholder: '不关联(可选)',
       titlePlaceholder: '输入任务标题...',
diff --git a/src/router/index.ts b/src/router/index.ts
index ac308e7..9941946 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -106,7 +106,7 @@ const routes = [
 const router = createRouter({
   history: createWebHashHistory(),
   routes,
-  // LW-12:路由切换回到顶部(消除滚动残留:从长列表页切到短页仍停在原滚动位置)
+  // 路由切换回到顶部(消除滚动残留:从长列表页切到短页仍停在原滚动位置)
   scrollBehavior: () => ({ top: 0 }),
 })
 
diff --git a/src/stores/ai.ts b/src/stores/ai.ts
index abaa7b4..08ab0be 100644
--- a/src/stores/ai.ts
+++ b/src/stores/ai.ts
@@ -36,9 +36,9 @@ import type { AiChatEvent, AiConversationSummary, AiMessage, AiProviderConfig, A
 const MESSAGE_CAP = 200
 
 /**
- * 单对话 messages 的 parts 体积软上限(UX-260617-20)。
+ * 单对话 messages 的 parts 体积软上限。
  *
- * 背景:MESSAGE_CAP 仅按"条数"裁剪,不感知单条体积。多模态 base64 图片(F-260614-05)
+ * 背景:MESSAGE_CAP 仅按"条数"裁剪,不感知单条体积。多模态 base64 图片
  * 单条可达数 MB,极端 200 条 × 5MB ≈ 1GB 会撑爆渲染内存。
  *
  * 设计:
@@ -51,7 +51,7 @@ const PARTS_SIZE_BUDGET = 50 * 1024 * 1024 // 50MB(base64 char 计)
 import { useAiEvents } from '@/composables/ai/useAiEvents'
 import { useAiStream } from '@/composables/ai/useAiStream'
 // 批4 双轨收口:getConvState 从 aiShared 取(下沉破环,避免 stores/ai↔useAiEvents 反向环)。
-// F-09 per-conv streaming/currentText(DEC-07a 父④):convStreamStates Map + 辅助函数亦从 aiShared 取。
+// F-09 per-conv streaming/currentText(a 父④):convStreamStates Map + 辅助函数亦从 aiShared 取。
 import { getConvState, getConvStreamState, setConvStreaming, setConvCurrentText, __bindMessages, __bindState } from '@/composables/ai/aiShared'
 import { useAiSend, initDrainQueueListener } from '@/composables/ai/useAiSend'
 import { useAiApproval } from '@/composables/ai/useAiApproval'
@@ -62,7 +62,7 @@ import { useAiContext } from '@/composables/ai/useAiContext'
 
 /// 模块级单例 state — 全应用共享(composables 通过 `import { state } from '@/stores/ai'` 取用)
 //
-// F-09 B 路线 per-conv streaming/currentText(DEC-07a 父④):
+// F-09 B 路线 per-conv streaming/currentText(a 父④):
 // `streaming`/`currentText` 不再是全局单例字段,改为 accessor 委派 aiShared.convStreamStates
 // (per-conv Map,定义于 composables/ai/aiShared.ts)按 activeConversationId 索引读写。
 //
@@ -137,7 +137,7 @@ const _stateBase: {
   // F-260614-05 Phase 2b: parts 挂多模态图片片(供 drainQueue/force 续发时本地 user 消息渲染图)
   queue: [],
   // Agentic 循环当前轮次(0=非agentic/未开始;>0=收到后端 AiAgentRound 事件的 round)
-  // AE-2025-07:进度条用; AiCompleted/AiError 复位为 0
+  // 进度条用; AiCompleted/AiError 复位为 0
   agentRound: 0,
   completedTools: 0,
   // 归档分组折叠态(默认折叠)
@@ -252,7 +252,7 @@ function estimatePartsSize(parts: ContentPart[] | undefined): number {
 }
 
 /**
- * parts 体积软上限守卫(UX-260617-20):push 式增长使总 parts 体积超 PARTS_SIZE_BUDGET 时,
+ * parts 体积软上限守卫push 式增长使总 parts 体积超 PARTS_SIZE_BUDGET 时,
  * 从最旧消息起逐条置空其 parts(丢历史多模态图,保文本/条数)。
  *
  * 仅拦截 push(同上 length watch 的判定),避免切换对话整体替换时误裁后端历史图。
@@ -319,12 +319,12 @@ function isGenerating(convId: string | null | undefined): boolean {
  * (composable 内部全部是模块级单例实现,展开的只是引用,无重复实例化开销)。
  */
 export function useAiStore() {
-  // ARC-06: 启动 drain-queue 事件监听(由 useAiEvents AiCompleted 经事件总线触发)
+  // 启动 drain-queue 事件监听(由 useAiEvents AiCompleted 经事件总线触发)
   void initDrainQueueListener()
   return {
     state,
     filteredConversations,
-    // F-09: 生成态 helper(批4 双轨收口:仅读 isGenerating 派生自 getConvState;
+    // 生成态 helper(批4 双轨收口:仅读 isGenerating 派生自 getConvState;
     // addGenerating/removeGenerating 写 helper 已删,写收敛到 useAiEvents setConvState)
     isGenerating,
     ...useAiEvents(),
diff --git a/src/stores/knowledge.ts b/src/stores/knowledge.ts
index 41e7e1f..33fede8 100644
--- a/src/stores/knowledge.ts
+++ b/src/stores/knowledge.ts
@@ -150,7 +150,7 @@ export function useKnowledgeStore() {
   }
 
   async function updateStatus(id: string, status: string): Promise {
-    // WK-3:返成功布尔(失败时 runWithCatch 已置 state.error),调用方据此中止推进/报错
+    // 返成功布尔(失败时 runWithCatch 已置 state.error),调用方据此中止推进/报错
     const ok = await runWithCatch(state, t('knowledge.err.updateStatusFailed'), async () => {
       await knowledgeApi.updateStatus(id, status)
       // 从 items 和 candidates 中同步移除
diff --git a/src/stores/project.ts b/src/stores/project.ts
index 8e49061..8af43e7 100644
--- a/src/stores/project.ts
+++ b/src/stores/project.ts
@@ -28,7 +28,7 @@ function createStore() {
   // "df-data-changed" { entity, action },前端按 entity 调对应 load 刷新列表,
   // 取代工具执行后需手动刷新。
   //
-  // CR-260615-26(R-1): task 分支不再无参 loadTasks(会把 store.tasks 冲成全量,破坏
+  // task 分支不再无参 loadTasks(会把 store.tasks 冲成全量,破坏
   // B-260615-29「store.tasks 反映当前筛选」契约)。改为读 _activeTaskProject:
   //   - undefined: Tasks 视图未挂载/从未设过筛选 → 不触发 task load(避免破坏筛选,
   //     让 B-29 watch/handle 主导;此时改动由用户下次进 Tasks 自然刷新)
@@ -41,7 +41,7 @@ function createStore() {
   // 避免刷新把已选 status 冲掉)。
   let _activeTaskProject: string | undefined = undefined
   let _activeTaskStatus: string | undefined = undefined
-  // WK-10:Tasks 视图搜索关键字登记(数据变更联动刷新时一并下沉 keyword,避免冲掉搜索结果)
+  // Tasks 视图搜索关键字登记(数据变更联动刷新时一并下沉 keyword,避免冲掉搜索结果)
   let _activeTaskKeyword: string | undefined = undefined
   function setActiveTaskProject(projectKey: string | undefined) {
     _activeTaskProject = projectKey
@@ -62,7 +62,7 @@ function createStore() {
     return query
   }
 
-  // WK-4:Ideas 视图当前查询登记(数据变更联动刷新按当前筛选重载,不再无参 loadIdeas 全量冲掉筛选)。
+  // Ideas 视图当前查询登记(数据变更联动刷新按当前筛选重载,不再无参 loadIdeas 全量冲掉筛选)。
   // undefined = Ideas 视图未挂载(如 Dashboard)→ 退化为全量刷新,保持面板统计新鲜。
   let _activeIdeaQuery: IdeaQuery | undefined = undefined
   function setActiveIdeaQuery(q: IdeaQuery | undefined) {
@@ -75,7 +75,7 @@ function createStore() {
     if (_dataChangedUnlisten) return _dataChangedUnlisten
     _dataChangedUnlisten = await listen('df-data-changed', (event) => {
       const { entity } = event.payload
-      // 按 entity 调对应 load 刷新(CR-260615-19:action 当前未消费,为未来 delete 本地移除等差异化刷新预留,统一 load 全量拉取)
+      // 按 entity 调对应 load 刷新action 当前未消费,为未来 delete 本地移除等差异化刷新预留,统一 load 全量拉取)
       if (entity === 'project') {
         void projectsStore.loadProjects()
       } else if (entity === 'task') {
@@ -89,7 +89,7 @@ function createStore() {
         // Dashboard 数字(与 Tasks 视图分页/筛选解耦,不受 q undefined 影响)
         void loadStats()
       } else if (entity === 'idea') {
-        // WK-4:按 Ideas 视图当前 query 重载(未挂载时退化为全量刷新 Dashboard 面板)
+        // 按 Ideas 视图当前 query 重载(未挂载时退化为全量刷新 Dashboard 面板)
         void ideasStore.loadIdeas(_activeIdeaQuery)
       }
     })
@@ -182,20 +182,20 @@ function createStore() {
     startEventListener: workflowStore.startEventListener,
     stopEventListener: workflowStore.stopEventListener,
     clearLiveEvents: workflowStore.clearLiveEvents,
-    // B-41: 工作流进度共享推导(按 execId 派生节点状态/进度)
+    // 工作流进度共享推导(按 execId 派生节点状态/进度)
     workflowProgress: workflowStore.workflowProgress,
     approveHumanApproval: workflowStore.approveHumanApproval,
     cancelHumanApproval: workflowStore.cancelHumanApproval,
     // AR-11(方案A):数据变更联动刷新监听(后端工具执行后 emit df-data-changed)
     startDataChangedListener,
     stopDataChangedListener,
-    // CR-260615-26(R-1): Tasks 视图登记当前 activeProject 筛选,供 listener 按筛选拉取
+    // asks 视图登记当前 activeProject 筛选,供 listener 按筛选拉取
     setActiveTaskProject,
     // F-260621-02(P1 status 下沉):Tasks 视图登记当前 status 筛选,供 listener 复用同一筛选拉取
     setActiveTaskStatus,
-    // WK-10:Tasks 视图登记当前搜索关键字,供 listener 复用同一筛选拉取
+    // Tasks 视图登记当前搜索关键字,供 listener 复用同一筛选拉取
     setActiveTaskKeyword,
-    // WK-4: Ideas 视图登记当前查询,供 listener 按筛选重载(不冲掉筛选视图)
+    // Ideas 视图登记当前查询,供 listener 按筛选重载(不冲掉筛选视图)
     setActiveIdeaQuery,
     pendingApproval: computed(() => state.pendingApproval),
     // computed
diff --git a/src/stores/project/ideas.ts b/src/stores/project/ideas.ts
index deb7426..fa02439 100644
--- a/src/stores/project/ideas.ts
+++ b/src/stores/project/ideas.ts
@@ -9,11 +9,11 @@ export function createIdeasStore() {
   /**
    * 加载灵感列表。
    *
-   * F-260621-02:status/keyword/order_by 下沉后端 WHERE,取代前端 computed filter。
+   * status/keyword/order_by 下沉后端 WHERE,取代前端 computed filter。
    * - 传 `query` → 多条件查询(状态/关键词/排序/分页)。
    * - 不传 → 全量(created_at DESC,等价旧行为)。
    *
-   * WK-7:成功时清 state.error(load 后不残留旧错误),失败由 runWithCatch 置 error。
+   * 成功时清 state.error(load 后不残留旧错误),失败由 runWithCatch 置 error。
    */
   async function loadIdeas(query?: IdeaQuery) {
     state.error = null
@@ -40,7 +40,7 @@ export function createIdeasStore() {
   }
 
   async function deleteIdea(id: string): Promise {
-    // WK-7:返成功布尔(失败时 runWithCatch 已置 state.error),调用方据此保留选中态
+    // 返成功布尔(失败时 runWithCatch 已置 state.error),调用方据此保留选中态
     const ok = await runWithCatch(state, t('ideas.err.deleteFailed'), async () => {
       await ideaApi.delete(id)
       state.ideas = state.ideas.filter(i => i.id !== id)
@@ -57,7 +57,7 @@ export function createIdeasStore() {
   }
 
   async function promoteIdea(id: string) {
-    // WK-4:不再内部全量 loadIdeas(会把 store.ideas 冲成全集、冲掉当前筛选视图)。
+    // 不再内部全量 loadIdeas(会把 store.ideas 冲成全集、冲掉当前筛选视图)。
     // 由视图(Ideas.vue promoteToProject)操作后按当前 buildIdeaQuery() 主动重载。
     return await ideaApi.promote(id)
   }
@@ -65,7 +65,7 @@ export function createIdeasStore() {
   async function relateIdeas(subjectId: string, targetIds: string[]) {
     await runWithCatch(state, t('ideas.err.relateFailed'), async () => {
       await ideaApi.relateIdeas(subjectId, targetIds)
-      // WK-4:后端原子更新受影响灵感,原全量 loadIdeas 会冲掉当前筛选视图;
+      // 后端原子更新受影响灵感,原全量 loadIdeas 会冲掉当前筛选视图;
       // 改由视图(Ideas.vue onUpdateRelated)按当前 query 重载。
     })
   }
diff --git a/src/stores/project/projects.ts b/src/stores/project/projects.ts
index 94b87b4..ec1e921 100644
--- a/src/stores/project/projects.ts
+++ b/src/stores/project/projects.ts
@@ -8,7 +8,7 @@ import { state, clearError } from './state'
 export function createProjectsStore() {
   /**
    * 加载未删除项目。
-   * F-260621-02:可选 query 透传(关键词/排序/分页)。不传 = 当前全量行为(向后兼容)。
+   * 可选 query 透传(关键词/排序/分页)。不传 = 当前全量行为(向后兼容)。
    */
   async function loadProjects(query?: ProjectQuery) {
     state.loading = true
diff --git a/src/stores/project/tasks.ts b/src/stores/project/tasks.ts
index c4566e8..8183281 100644
--- a/src/stores/project/tasks.ts
+++ b/src/stores/project/tasks.ts
@@ -6,14 +6,14 @@ import { state } from './state'
 
 /** 任务 CRUD 子 store(共享全局 state) */
 export function createTasksStore() {
-  // WK-10:并发 loadTasks 竞态守卫(快速切筛选/搜索防旧响应晚到覆盖新结果,
+  // 并发 loadTasks 竞态守卫(快速切筛选/搜索防旧响应晚到覆盖新结果,
   // 对齐 knowledge store 的 _itemsSeq 模式)。旧请求的失败也不污染当前视图。
   let _tasksSeq = 0
 
   /**
    * 加载任务列表。
    *
-   * **双签名向后兼容**(F-260621-02):
+   * **双签名向后兼容**
    * - `loadTasks(projectId?)`:旧签名,仅项目过滤(等价 `loadTasks({ project_id: projectId })`)。
    * - `loadTasks(query?)`:新签名,多条件(status/keyword/...)走后端 WHERE。
    *
@@ -58,7 +58,7 @@ export function createTasksStore() {
   }
 
   async function deleteTask(id: string): Promise {
-    // WK-13:返成功布尔(失败时 runWithCatch 已置 state.error),快捷删除据此 toast 反馈
+    // 返成功布尔(失败时 runWithCatch 已置 state.error),快捷删除据此 toast 反馈
     const ok = await runWithCatch(state, t('tasks.err.deleteFailed'), async () => {
       // 后端已级联软删子任务(delete_task 返回 { ok, cascaded });
       // 本地同步移除父任务 + 其直接子任务(1 级嵌套,防悬挂 parent_id)
diff --git a/src/stores/project/workflow.ts b/src/stores/project/workflow.ts
index a5bcf3a..e61c155 100644
--- a/src/stores/project/workflow.ts
+++ b/src/stores/project/workflow.ts
@@ -3,7 +3,7 @@ import { t } from '@/i18n/i18n-helpers'
 import type { NodeStatus, WorkflowEventPayload } from '@/api/types'
 import { state, _eventUnlisten, setEventUnlisten } from './state'
 
-// B-41: 工作流进度共享推导。liveEvents 已全局收集所有 workflow-event,
+// 工作流进度共享推导。liveEvents 已全局收集所有 workflow-event,
 // 此处按 execution_id 重放派生节点状态/进度,供 TaskDetail/ProjectDetail 同源使用,
 // 替代各自维护一份 onEvent 累加状态(单事实源,并发工作流互不干扰)。
 export interface WorkflowProgress {
@@ -74,7 +74,7 @@ function deriveWorkflowProgress(
   return { nodeStatuses, runningNode, doneCount, totalNodes, result }
 }
 
-// B-03b-R10(⑦ 互斥): 审批响应/取消 IPC 进行中标记(模块级单例,createWorkflowStore 每会话只跑一次)。
+// b-R10(⑦ 互斥): 审批响应/取消 IPC 进行中标记(模块级单例,createWorkflowStore 每会话只跑一次)。
 // 防双击/连点在同一轮 IPC 未返回前发起第二次(后端无幂等键,第二次会重复消费 Request 触发
 // "node already completed" 类报错)。store 层守卫优于按钮 disabled: 共享单例 state,跨调用方统一。
 let _approvalInFlight = false
@@ -112,18 +112,18 @@ export function createWorkflowStore() {
     if (_eventUnlisten) return _eventUnlisten
     const unlisten = await workflowApi.onEvent((payload) => {
       try {
-        // 固时间戳(FR-C1):formattedEvents computed 重算时用事件发生时刻,非当前时刻
+        // 固时间戳formattedEvents computed 重算时用事件发生时刻,非当前时刻
         state.liveEvents.push({ ...payload, _ts: Date.now() })
-        // 限长(FR-R3):防 liveEvents 无限增长撑爆内存,保留最近 200 条
+        // 限长防 liveEvents 无限增长撑爆内存,保留最近 200 条
         if (state.liveEvents.length > 200) {
           state.liveEvents.splice(0, state.liveEvents.length - 200)
         }
         // 处理人工审批请求
-        // WorkflowEvent serde tag=type rename_all=snake_case → type='human_approval_request',字段扁平(无 data 包装)(B-03b-R7)
+        // WorkflowEvent serde tag=type rename_all=snake_case → type='human_approval_request',字段扁平(无 data 包装)(b-R7)
         if (payload.event?.type === 'human_approval_request') {
           state.pendingApproval = payload.event as unknown as typeof state.pendingApproval
         }
-        // B-03b-R10(⑤ 终态不清): 工作流走到终态(completed/failed)或审批节点被取消时,
+        // b-R10(⑤ 终态不清): 工作流走到终态(completed/failed)或审批节点被取消时,
         // 若 pendingApproval 仍属于本次 execution,说明该审批未经 approve/cancel 正常路径关闭
         // (上游节点失败/工作流被整体取消/用户「稍后」关闭弹窗但 state 仍残留),清掉陈旧态
         // 避免下次进 ProjectDetail 时 watch immediate 误判仍有待审批。
@@ -150,7 +150,7 @@ export function createWorkflowStore() {
   }
 
   /**
-   * B-41: 按 execution_id 派生工作流进度(读全局 liveEvents,响应式)。
+   * 按 execution_id 派生工作流进度(读全局 liveEvents,响应式)。
    * 供 TaskDetail(自身推进的 execId)/ ProjectDetail(最近一次执行)共用同源状态。
    */
   function workflowProgress(execId: string | null): WorkflowProgress {
@@ -165,25 +165,25 @@ export function createWorkflowStore() {
    * - 多选(select_type='multiple'):decisions 透传全量。
    *
    * CR-11⑪:签名保持 approveHumanApproval(decisions: string[], comment?)。
-   * F-260615-01: decisions/select_type 透传,与后端 IPC 签名对齐。
+   * decisions/select_type 透传,与后端 IPC 签名对齐。
    */
   async function approveHumanApproval(
     decisions: string[],
     comment?: string,
   ) {
     if (!state.pendingApproval) return
-    // B-03b-R10(⑦ 互斥): 上一轮 IPC 未返回,忽略重复点击(连点/双击)
+    // b-R10(⑦ 互斥): 上一轮 IPC 未返回,忽略重复点击(连点/双击)
     if (_approvalInFlight) return
     _approvalInFlight = true
     const selectType = state.pendingApproval.select_type ?? 'single'
-    // CR-260615-18:删死逻辑三元(原 selectType 多/单分支返回值相同)
+    // 删死逻辑三元(原 selectType 多/单分支返回值相同)
     // 单选取首项(后端 decision 单值);多选 decision 取首项占位(后端按 decisions 走)
     const decision = decisions[0] ?? ''
 
     try {
-      // R-PD-5: 透传 options 闭环后端校验(options 非空时 decision 必须 ∈ options,否则 IPC 报错)。
+      // 透传 options 闭环后端校验(options 非空时 decision 必须 ∈ options,否则 IPC 报错)。
       // options 来自已收到的 HumanApprovalRequest 事件 payload(IPC 无法访问节点 config)。
-      // B-260615-34:snake_case 由 api 层对齐后端 workflow.rs:211
+      // snake_case 由 api 层对齐后端 workflow.rs:211
       await workflowApi.approveHumanApproval({
         execution_id: state.pendingApproval.execution_id,
         node_id: state.pendingApproval.node_id,
@@ -200,7 +200,7 @@ export function createWorkflowStore() {
       console.error('审批失败:', error)
       state.error = error?.toString() ?? t('projects.err.approveFailed')
     } finally {
-      // B-03b-R10(⑦ 互斥): 无论成功/失败,IPC 已返回,释放重入锁
+      // b-R10(⑦ 互斥): 无论成功/失败,IPC 已返回,释放重入锁
       _approvalInFlight = false
     }
   }
@@ -208,7 +208,7 @@ export function createWorkflowStore() {
   /** 取消当前待审批节点(调 cancel_workflow_node IPC 置节点 Cancelled) */
   async function cancelHumanApproval() {
     if (!state.pendingApproval) return
-    // B-03b-R10(⑦ 互斥): 上一轮 IPC 未返回,忽略重复点击
+    // b-R10(⑦ 互斥): 上一轮 IPC 未返回,忽略重复点击
     if (_approvalInFlight) return
     _approvalInFlight = true
 
@@ -223,7 +223,7 @@ export function createWorkflowStore() {
       console.error('取消审批失败:', error)
       state.error = error?.toString() ?? t('projects.err.cancelFailed')
     } finally {
-      // B-03b-R10(⑦ 互斥): 无论成功/失败,IPC 已返回,释放重入锁
+      // b-R10(⑦ 互斥): 无论成功/失败,IPC 已返回,释放重入锁
       _approvalInFlight = false
     }
   }
diff --git a/src/utils/ideaEval.ts b/src/utils/ideaEval.ts
index ca03f2a..cbdfd66 100644
--- a/src/utils/ideaEval.ts
+++ b/src/utils/ideaEval.ts
@@ -64,7 +64,7 @@ const ASSESSMENT_CLASS_MAP: Record = {
  * recommendation 字符串 → CSS badge 类。
  * 未知值(或非字符串/空)回退到 'conditional'(中性,对齐原两处实现)。
  *
- * WK-15:旧实现对 undefined/null 调 `.toLowerCase()` 崩。对抗评估 ai_analysis
+ * 旧实现对 undefined/null 调 `.toLowerCase()` 崩。对抗评估 ai_analysis
  * 可能未生成 recommendation 字段(老数据/降级),调用方传值时也可能漏判空。
  * 这里在源头做防御:非字符串/空串统一回退 conditional。
  */
@@ -77,7 +77,7 @@ export function assessmentClass(recommendation: string | null | undefined): stri
  * recommendation 字符串 → i18n 本地化标签。
  * 命中 ideas.assessment.{recommendation} 则返回译文,否则回退到原始 recommendation。
  *
- * WK-15:非字符串/空 → 回退占位符 '—'(避免模板渲染 undefined),与详情页其他空值一致。
+ * 非字符串/空 → 回退占位符 '—'(避免模板渲染 undefined),与详情页其他空值一致。
  * @param t vue-i18n 的 t 函数(useI18n().t),由调用方注入避免本模块耦合 i18n 实例
  */
 export function assessmentLabel(
diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts
index 38d084c..8c20c8f 100644
--- a/src/utils/markdown.ts
+++ b/src/utils/markdown.ts
@@ -5,7 +5,7 @@
  * 仅去除粗体、斜体、删除线、标题、行内代码、代码块围栏、列表、引用、
  * 链接、图片等 Markdown 语法标记,保留文字内容。
  *
- * 动机(UX-260618-13):列表摘要用纯文本插值显示 AI 生成内容,
+ * 动机列表摘要用纯文本插值显示 AI 生成内容,
  * 数据源含 Markdown 语法字符会裸字符外露,列表空间小且配合截断,
  * 剥离比富文本渲染更合适。
  */
diff --git a/src/utils/time.ts b/src/utils/time.ts
index 5fec6bf..531d4ef 100644
--- a/src/utils/time.ts
+++ b/src/utils/time.ts
@@ -13,7 +13,7 @@
 
 import { t, currentLocale } from '@/i18n/i18n-helpers'
 
-// CR-260615-08(P1-10):相对时间走 i18n(原硬编码中文,en locale 时间恒中文)。
+// CR-260615-08相对时间走 i18n(原硬编码中文,en locale 时间恒中文)。
 // 类型安全访问经 i18n-helpers 收口(规避 vue-i18n TS2589)。
 
 /** 解析任意时间值为毫秒时间戳;无效或空返回 null(仅本模块内部使用) */
diff --git a/src/views/AuditLog.vue b/src/views/AuditLog.vue
index ab9fc5b..5d53cbe 100644
--- a/src/views/AuditLog.vue
+++ b/src/views/AuditLog.vue
@@ -419,7 +419,7 @@ function decidedClass(d: string): string {
   return d === 'human' ? 'decided-human' : 'decided-auto'
 }
 
-// SA-2:对话角色 i18n 化(原硬编码 '🧑 用户'/'🤖 助手'/'🔧 工具'/'⚙️ 系统' 不随 locale)
+// 对话角色 i18n 化(原硬编码 '🧑 用户'/'🤖 助手'/'🔧 工具'/'⚙️ 系统' 不随 locale)
 // user/assistant/tool 映射固定 key,其他未知 role 回退到 '⚙️ 系统'(含 emoji,与其他四态视觉一致)
 function convRoleLabel(role: string): string {
   if (role === 'user') return t('auditLog.convRoleUser')
diff --git a/src/views/Ideas.vue b/src/views/Ideas.vue
index 99fe619..739f248 100644
--- a/src/views/Ideas.vue
+++ b/src/views/Ideas.vue
@@ -228,7 +228,7 @@ function statusLabelKey(status: IdeaStatus): string {
   return statusOptions.find(s => s.value === status)?.labelKey ?? status
 }
 
-// ── 后端查询构造(F-260621-02:status/keyword/order_by 下沉后端 WHERE) ──
+// ── 后端查询构造status/keyword/order_by 下沉后端 WHERE) ──
 //
 // status 下沉:单一状态(all→不限 / promoted→promoted)走后端精确匹配;
 // hot(按 score≥80)与 pending(多状态 draft+pending_review)无法映射单一后端 status,
@@ -249,7 +249,7 @@ function buildIdeaQuery(): IdeaQuery {
   return q
 }
 
-// WK-4:按当前视图查询加载(登记 setActiveIdeaQuery 供 df-data-changed 联动刷新复用同一筛选,
+// 按当前视图查询加载(登记 setActiveIdeaQuery 供 df-data-changed 联动刷新复用同一筛选,
 // 避免 promote/relate/监听触发无参 loadIdeas 全量冲掉当前筛选视图)
 function loadCurrentView() {
   store.setActiveIdeaQuery(buildIdeaQuery())
@@ -331,14 +331,14 @@ watch([filteredIdeas, () => selectedId.value], () => {
   if (targetPage !== page.value) page.value = targetPage
 })
 
-// WK-12:分页越界自愈(删除/筛选变化后 page 超界 → 钳制到最后一页,防空白页)
+// 分页越界自愈(删除/筛选变化后 page 超界 → 钳制到最后一页,防空白页)
 watch([filteredIdeas, pageSize], () => {
   if (pageSize.value <= 0) return
   const maxPage = Math.max(1, Math.ceil(filteredIdeas.value.length / pageSize.value))
   if (page.value > maxPage) page.value = maxPage
 })
 
-// B-260615-25:灵感描述 Markdown 渲染已下沉至 IdeaDetail 子组件(共享模块单例渲染器)
+// 灵感描述 Markdown 渲染已下沉至 IdeaDetail 子组件(共享模块单例渲染器)
 
 // 评分档位 class(高/中/低 三色标),阈值统一走 scoreTier(单一来源,消除 4 处重复)
 function scoreClass(score: number | null) {
@@ -369,7 +369,7 @@ async function confirmCapture() {
       tags: JSON.stringify(tags),
       source: newIdeaSource.value.trim() || undefined,
     })
-    // WK-18:createIdea 返 null=失败(state.error 已置,顶部 error-banner 展示);成功关弹窗 + toast
+    // createIdea 返 null=失败(state.error 已置,顶部 error-banner 展示);成功关弹窗 + toast
     if (created) {
       showCaptureModal.value = false
       Message.success(t('ideas.createdToast'))
@@ -384,7 +384,7 @@ async function deleteCurrentIdea() {
   if (!await confirmDialog(t('ideas.confirmDelete', { title: currentIdea.value.title }))) return
   deleting.value = true
   try {
-    // WK-7:删除失败不清理选中态(错误走 error-banner),仅成功时清空详情
+    // 删除失败不清理选中态(错误走 error-banner),仅成功时清空详情
     const ok = await store.deleteIdea(currentIdea.value.id)
     if (ok) selectedId.value = null
   } finally {
@@ -397,7 +397,7 @@ async function promoteToProject() {
   promoting.value = true
   try {
     const res = await store.promoteIdea(currentIdea.value.id)
-    // WK-4:promote 后按当前视图重载(store 不再内部全量 loadIdeas 冲掉筛选)
+    // promote 后按当前视图重载(store 不再内部全量 loadIdeas 冲掉筛选)
     await loadCurrentView()
     router.push(`/projects/${res.project_id}`)
   } catch (e: any) {
@@ -433,11 +433,11 @@ async function evaluateCurrentIdea() {
 
 async function onStatusChange(newStatus: IdeaStatus) {
   if (!currentIdea.value) return
-  // WK-5:promoted 走独立立项流程(promoteToProject),状态菜单/下拉已排除,此处兜底禁止直接置
+  // promoted 走独立立项流程(promoteToProject),状态菜单/下拉已排除,此处兜底禁止直接置
   if (newStatus === 'promoted') return
   // ⑥ 状态切换确认:落到 promoted/rejected/archived 等终态(或离开 approved 失去立项入口)
   // 需用户确认,避免误点。中性流转(draft↔pending_review↔approved)直接落库。
-  // (WK-5:上方已拦截 promoted,此处不再重复比对,TS 收窄后 'promoted' 无交集)
+  // 上方已拦截 promoted,此处不再重复比对,TS 收窄后 'promoted' 无交集)
   const from = currentIdea.value.status
   const needsConfirm = newStatus === 'rejected' || newStatus === 'archived'
     || (from === 'approved' && newStatus !== 'approved')
@@ -453,7 +453,7 @@ async function onStatusChange(newStatus: IdeaStatus) {
 }
 
 // 接收 IdeaDetail 子组件 emit 的 'update-desc'(描述编辑保存)
-// WK-16:返 boolean(成功 true/失败 false),失败 toast;子组件据 false 保持编辑态。
+// 返 boolean(成功 true/失败 false),失败 toast;子组件据 false 保持编辑态。
 async function onUpdateDesc(desc: string): Promise {
   if (!currentIdea.value) return false
   try {
@@ -470,12 +470,12 @@ async function onUpdateDesc(desc: string): Promise {
 async function onUpdateRelated(ids: string[]) {
   if (!currentIdea.value) return
   await store.relateIdeas(currentIdea.value.id, ids)
-  // WK-4:relate 后端原子更新受影响灵感,按当前视图重载(store 不再内部全量 loadIdeas 冲筛选)
+  // relate 后端原子更新受影响灵感,按当前视图重载(store 不再内部全量 loadIdeas 冲筛选)
   await loadCurrentView()
 }
 
 // 接收 IdeaDetail 子组件 emit 的 'update-tags'(标签编辑保存)
-// WK-16:返 boolean(成功 true/失败 false),失败 toast;子组件据 false 保持编辑态。
+// 返 boolean(成功 true/失败 false),失败 toast;子组件据 false 保持编辑态。
 async function onUpdateTags(tags: string): Promise {
   if (!currentIdea.value) return false
   try {
diff --git a/src/views/Knowledge.vue b/src/views/Knowledge.vue
index ba8c45e..841120c 100644
--- a/src/views/Knowledge.vue
+++ b/src/views/Knowledge.vue
@@ -241,7 +241,7 @@ function getCategoryCount(key: string): number {
 // ===== 详情选中 + 加载 =====
 const selectedId = ref(null)
 const detail = ref(null)
-// B-260615-25:Markdown 渲染器预热保留在父级(模块单例,与 AiChat/TaskDetail/KnowledgeDetail 共享),
+// Markdown 渲染器预热保留在父级(模块单例,与 AiChat/TaskDetail/KnowledgeDetail 共享),
 // 详情内容渲染由 KnowledgeDetail 子组件通过 useRendered 消费,此处仅 ensureLoaded 预热不阻塞首屏
 const { ensureLoaded } = useRendered(() => '')
 
@@ -286,8 +286,8 @@ async function onDetailSave(payload: { id: string; data: { title: string; conten
 
 // ===== 审核操作(收件箱) =====
 // P0-②:发布/拒绝/归档后自动选中下一条(而非清空详情),减少审核者来回点击
-// WK-2:index 须在 store 操作前快照(操作后 candidates 已 filter 移除 currentId,findIndex 恒 -1)
-// WK-3:操作失败(updateStatus/archive 返 false)toast 报错 + 中止推进(不 selectNext,不清详情)
+// index 须在 store 操作前快照(操作后 candidates 已 filter 移除 currentId,findIndex 恒 -1)
+// 操作失败(updateStatus/archive 返 false)toast 报错 + 中止推进(不 selectNext,不清详情)
 async function publishCurrent() {
   if (!detail.value) return
   const currentId = detail.value.knowledge.id
diff --git a/src/views/ProjectDetail.vue b/src/views/ProjectDetail.vue
index 89efd07..54effd1 100644
--- a/src/views/ProjectDetail.vue
+++ b/src/views/ProjectDetail.vue
@@ -556,14 +556,13 @@ const currentProject = computed(() =>
 const loading = computed(() => store.projects.length === 0 && !store.error)
 
 // 状态文案/阶段进度统一走 ../constants/project
-// B-260615-25:项目描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例),
+// 项目描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例),
 // useRendered 封装 computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
 const { rendered: renderedDesc, ensureLoaded } = useRendered(
   () => currentProject.value?.description ?? '',
 )
 const stageKey = computed(() => currentProject.value?.status ?? 'planning')
 const statusLabel = computed(() => projectStatusLabel(stageKey.value))
-// const currentStageIndex = computed(() => projectStageInfo(stageKey.value).stepIndex)
 
 // ── 来源灵感(晋升携带评估结论回溯)──
 // ProjectRecord.idea_id 存灵感 id(promote_idea 写入)。
@@ -850,7 +849,7 @@ async function onScanSubmodules() {
 }
 
 // ── 工作流 ──
-// demoDag + runDemoWorkflow 已下线(R-PD-2):"script" 节点不再注册到 NodeRegistry,
+// demoDag + runDemoWorkflow 已下线"script" 节点不再注册到 NodeRegistry,
 // 前端构造含 script 节点的 DagDef 会在后端 build_dag 失败报错。
 // 工作流执行日志面板保留,实时事件展示能力不依赖 demoDag。
 // 待真实工作流需求落地(独立 BuildNode + 审批链)时重建演示入口。
@@ -868,7 +867,7 @@ const formattedEvents = computed(() => {
     const type = ev.type ?? 'unknown'
     const level = type.includes('error') || type.includes('fail') ? 'error'
       : type.includes('warn') ? 'warn' : 'info'
-    // 用事件入数组时固化的时间戳(FR-C1),非 computed 重算的当前时刻
+    // 用事件入数组时固化的时间戳,非 computed 重算的当前时刻
     const time = new Date(evt._ts).toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit', second: '2-digit' })
     const message = ev.label
       ? `[${ev.label}] ${type}: ${ev.output ?? ev.code ?? ev.message ?? JSON.stringify(ev)}`
@@ -1013,8 +1012,8 @@ async function handleImportDir() {
 // ── 审批处理 ──
 // 审批对话框 UI + 单/多选决策逻辑抽离至 components/project/ApprovalDialog.vue
 // handleApproval/handleApprovalMulti/isMultipleSelect/multiDecisions 已迁移;
-// submitting(UX-260618-17)在子组件自治,父级 submitting 仅用于 submitNewTask
-// P0-C:ApprovalDialog + watch pendingApproval + handleCancelApproval 已提至 App.vue(全局),
+// submitting在子组件自治,父级 submitting 仅用于 submitNewTask
+// ApprovalDialog + watch pendingApproval + handleCancelApproval 已提至 App.vue(全局),
 // TaskDetail 等无 ApprovalDialog 的页面自动受益(原局部渲染致 HumanNode 超时失败)
 
 // ── 删除项目(软删 → 回收站,可恢复)──
@@ -1115,7 +1114,7 @@ onUnmounted(() => {
   gap: var(--df-gap-grid);
 }
 
-/* B-260615-31:字段同行布局(label 固定宽 + 值占余),描述字段 info-block 保持块状)
+/* 字段同行布局(label 固定宽 + 值占余),描述字段 info-block 保持块状)
    基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口 B-260619),
    此处仅保留本组件特有覆盖。 */
 .info-item a.idea-link {
diff --git a/src/views/Projects.vue b/src/views/Projects.vue
index 4237b7a..8bfa692 100644
--- a/src/views/Projects.vue
+++ b/src/views/Projects.vue
@@ -219,7 +219,7 @@ const pagedProjects = computed(() => {
   return store.projects.slice(start, start + pageSize.value)
 })
 
-// WK-12:分页越界自愈(删除/回收后 store.projects 数量变化,page 超界 → 钳制到最后一页,防空白页)
+// 分页越界自愈(删除/回收后 store.projects 数量变化,page 超界 → 钳制到最后一页,防空白页)
 watch([() => store.projects, pageSize], () => {
   if (pageSize.value <= 0) return
   const maxPage = Math.max(1, Math.ceil(store.projects.length / pageSize.value))
@@ -598,6 +598,6 @@ async function runImport() {
 }
 .toast-info { background: var(--df-accent); color: #fff; }
 .toast-error { background: var(--df-danger); color: #fff; }
-/* P0-2: ToastType 新增 'success' 兜底样式(本视图当前未用,补全避免裸类) */
+/* ToastType 新增 'success' 兜底样式(本视图当前未用,补全避免裸类) */
 .toast-success { background: var(--df-success); color: #fff; }
 
diff --git a/src/views/TaskDetail.vue b/src/views/TaskDetail.vue
index 494653a..82027b6 100644
--- a/src/views/TaskDetail.vue
+++ b/src/views/TaskDetail.vue
@@ -312,14 +312,14 @@ import { useTaskBatchActions } from '@/composables/task/useTaskBatchActions'
 const { t } = useI18n()
 const route = useRoute()
 const router = useRouter()
-// B-41: 共享 workflow store(liveEvents 全局收集 + workflowProgress 派生进度)
+// 共享 workflow store(liveEvents 全局收集 + workflowProgress 派生进度)
 const store = useProjectStore()
 
 const loading = ref(true)
 const errorMsg = ref('')
 const task = ref(null)
 const projects = ref([])
-// F-260619-01:灵感列表用于解析 task.idea_id → 灵感 title(独立入口,同 projects 模式)
+// 灵感列表用于解析 task.idea_id → 灵感 title(独立入口,同 projects 模式)
 const ideas = ref([])
 const advancing = ref(false)
 
@@ -471,7 +471,7 @@ async function setChildPriority(child: TaskRecord, priority: number) {
   }
 }
 
-// F-260805:子任务新建小弹窗
+// 子任务新建小弹窗
 const showSubtaskModal = ref(false)
 const subtaskTitle = ref('')
 const subtaskDesc = ref('')
@@ -519,7 +519,7 @@ async function confirmSubtask() {
 const wfAdvancing = ref(false)
 const wfExecId = ref(null)
 const wfResult = ref<'completed' | 'failed' | null>(null)
-// SW-260618-21: 终态提示 timer 引用,卸载时清理防写已销毁 ref(对齐 Projects.vue _toastTimer 模式)
+// 终态提示 timer 引用,卸载时清理防写已销毁 ref(对齐 Projects.vue _toastTimer 模式)
 let _wfResultTimer: ReturnType | null = null
 
 /** 重置工作流推进相关 UI 态(切任务/refresh/手动推进成功后调用。
@@ -531,7 +531,7 @@ function resetWorkflowUi() {
   if (_wfResultTimer) { clearTimeout(_wfResultTimer); _wfResultTimer = null }
 }
 
-// B-41: 共享工作流进度推导。workflow store 已全局收集 liveEvents,此处按 execId
+// 共享工作流进度推导。workflow store 已全局收集 liveEvents,此处按 execId
 // 派生节点状态/进度,与 ProjectDetail 同源(store.workflowProgress),替代原本地 onEvent 逐事件累加。
 const wfProgress = computed(() => store.workflowProgress(wfExecId.value))
 const wfNodeStatuses = computed(() => wfProgress.value.nodeStatuses)
@@ -567,7 +567,7 @@ async function refreshWorkflowDag(execId: string) {
 
 const taskId = computed(() => route.params.id as string)
 
-// B-24:任务描述 Markdown 渲染(复用 AiChat 同款渲染器,模块级单例),useRendered 封装
+// 任务描述 Markdown 渲染(复用 AiChat 同款渲染器,模块级单例),useRendered 封装
 // computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
 const { rendered: renderedDesc, ensureLoaded } = useRendered(
   () => task.value?.description ?? '',
@@ -604,7 +604,7 @@ async function measureDescHeight() {
   descOverflow.value = el.scrollHeight > DESC_COLLAPSE_PX
 }
 
-// P2-10: 描述编辑(对齐 IdeaDetail 模式;taskApi.update(id, 'description', value))
+// 描述编辑(对齐 IdeaDetail 模式;taskApi.update(id, 'description', value))
 const editingDesc = ref(false)
 const editDesc = ref('')
 const savingDesc = ref(false)
@@ -636,7 +636,7 @@ const projectName = computed(() => {
   return p?.name ?? task.value?.project_id ?? '—'
 })
 
-// F-260619-01:解析 task.idea_id → 灵感 title(找不到时回退 id,保证非空可点击)
+// 解析 task.idea_id → 灵感 title(找不到时回退 id,保证非空可点击)
 const ideaTitle = computed(() => {
   const ideaId = task.value?.idea_id
   if (!ideaId) return '—'
@@ -670,7 +670,7 @@ const advanceActions = computed(() => {
 // ADVANCE_MAP 各行的首个 primary 项:todo→in_progress, in_review→testing, testing→done, blocked→in_progress
 // in_progress 的 primary 是 in_review(无模板,不显示工作流推进按钮)。
 const WF_SUPPORTED_TARGETS = new Set(['in_progress', 'testing', 'done'])
-// CR-08-O1: blocked 是阻塞态(任务被阻塞),其 primary=in_progress 虽 ∈ WF_SUPPORTED_TARGETS,
+// blocked 是阻塞态(任务被阻塞),其 primary=in_progress 虽 ∈ WF_SUPPORTED_TARGETS,
 // 但 template_for('in_progress') 是「todo→in_progress 从头执行」拓扑(AiNode 等重新跑一遍),
 // blocked 恢复语义不该重跑 AiNode(任务已有上下文/进度,只需恢复非从头执行)。
 // 最小修法:blocked 不显示工作流推进按钮,用户手动 advance(blocked→in_progress 走后端状态机)或先解除阻塞。
@@ -693,7 +693,7 @@ async function handleAdvance(target: string) {
     // advance_task 返回更新后的 TaskRecord(含新 status / review_rounds)
     const updated = await taskApi.advance(task.value.id, target)
     task.value = updated
-    // P2-8: 手动推进成功后清工作流 UI 态(防上一轮工作流残留进度/终态提示污染新任务态)
+    // 手动推进成功后清工作流 UI 态(防上一轮工作流残留进度/终态提示污染新任务态)
     resetWorkflowUi()
     errorMsg.value = ''
   } catch (e: any) {
@@ -704,7 +704,7 @@ async function handleAdvance(target: string) {
 }
 
 // F-260616-06 ①-1: 工作流推进 — 传空 dag + target_status,后端自动选推进链模板。
-// B-41: 监听 workflow-event 显示轻量进度;完成/失败后任务态由后端回调推进(②-3/②-4),
+// 监听 workflow-event 显示轻量进度;完成/失败后任务态由后端回调推进(②-3/②-4),
 //       df-data-changed 自动刷新 task(AR-11 已通),此处不直接改 task.value。
 // name 用推进链标识(模板内未硬编码 task_id,运行时由后端 config 注入,前端只传 topology hint)。
 async function handleWorkflowAdvance(target: string) {
@@ -729,7 +729,7 @@ async function handleWorkflowAdvance(target: string) {
   }
 }
 
-// B-41: 进度状态已由共享 store 按 execId 派生(store.workflowProgress),不再本地逐事件累加。
+// 进度状态已由共享 store 按 execId 派生(store.workflowProgress),不再本地逐事件累加。
 // 原有 handleWorkflowEvent 已移除 —— 事件仍由 workflow store 全局收集,TaskDetail 只读派生结果。
 
 async function load() {
@@ -740,13 +740,13 @@ async function load() {
       taskApi.get(taskId.value),
       // 项目列表用于解析 project_id → 项目名(独立入口,不依赖全局 store)
       projectApi.list(),
-      // F-260619-01:灵感列表用于解析 idea_id → 灵感 title
+      // 灵感列表用于解析 idea_id → 灵感 title
       ideaApi.list(),
     ])
     task.value = t
     projects.value = ps
     ideas.value = ideasList
-    // F-260805:父子数据(子任务取父标题 / 顶层任务取子任务列表),与主数据并行
+    // 父子数据(子任务取父标题 / 顶层任务取子任务列表),与主数据并行
     await Promise.all([loadParentTitle(), loadChildren()])
   } catch (e: any) {
     task.value = null
@@ -769,7 +769,7 @@ watch(taskId, () => { resetWorkflowUi(); load() })
 watch(() => task.value?.id, () => { descExpanded.value = false })
 watch(renderedDesc, () => { measureDescHeight() })
 
-// B-260616-18: 数据变更联动刷新 unlistener(onMounted 注册,onBeforeUnmount 释放)
+// 数据变更联动刷新 unlistener(onMounted 注册,onBeforeUnmount 释放)
 // 对齐 AiChat _unlistenToolSlow 生命周期模式。本视图绕 store 直调 taskApi.get/projectApi.list,
 // 不享受 store 全局 df-data-changed 监听(该监听只刷 store.tasks 列表,不含本视图的当前 task 单体),
 // 故此本地监听 entity=task/project 时重载当前 task。
@@ -778,9 +778,9 @@ let _unlistenDataChanged: (() => void) | null = null
 onMounted(async () => {
   ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat 共享),不阻塞 load
   load()
-  // F-260805:点击子任务快捷菜单外关闭菜单
+  // 点击子任务快捷菜单外关闭菜单
   document.addEventListener('click', closeChildMenu)
-  // B-260616-18: 后端 AI 工具(create/update/delete 等)emit df-data-changed → 本视图重载当前 task
+  // 后端 AI 工具(create/update/delete 等)emit df-data-changed → 本视图重载当前 task
   // entity=task:任务本体字段(标题/状态/描述/分支…)被改时刷新;entity=project:项目名变更影响 projectName 解析时刷新
   try {
     _unlistenDataChanged = await listen('df-data-changed', (event) => {
@@ -792,7 +792,7 @@ onMounted(async () => {
   } catch (e) {
     console.error('[TaskDetail] 启动 df-data-changed 监听失败:', e)
   }
-  // B-41: 工作流进度由 workflow store 全局 liveEvents 推导(store.workflowProgress),
+  // 工作流进度由 workflow store 全局 liveEvents 推导(store.workflowProgress),
   // 此处只需确保 store 事件监听已启动(幂等,共享单例;按 execution_id 过滤由派生层完成)。
   try {
     await store.startEventListener()
@@ -803,9 +803,9 @@ onMounted(async () => {
 
 onBeforeUnmount(() => {
   if (_unlistenDataChanged) { _unlistenDataChanged(); _unlistenDataChanged = null }
-  // F-260805:移除子任务快捷菜单关闭监听
+  // 移除子任务快捷菜单关闭监听
   document.removeEventListener('click', closeChildMenu)
-  // SW-260618-21: 清终态提示 timer 防卸载后写已销毁 ref
+  // 清终态提示 timer 防卸载后写已销毁 ref
   if (_wfResultTimer) { clearTimeout(_wfResultTimer); _wfResultTimer = null }
 })
 
@@ -903,7 +903,7 @@ onBeforeUnmount(() => {
   flex-direction: column;
   gap: var(--df-gap-grid);
 }
-/* B-260615-31:字段同行布局(label 固定宽 + value 占余),描述字段 info-block 保持块状)
+/* 字段同行布局(label 固定宽 + value 占余),描述字段 info-block 保持块状)
    基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口 B-260619),
    此处仅保留本组件特有覆盖。 */
 /* B-24 漏 white-space 覆盖致 v-html 后 HTML 标签间 \n 被 pre-wrap 渲染为空行间距——移除 pre-wrap,描述由 .ai-md 接管(line-height 1.5 对齐 AiChat li)。
diff --git a/src/views/Tasks.vue b/src/views/Tasks.vue
index 20fa962..4cd79b6 100644
--- a/src/views/Tasks.vue
+++ b/src/views/Tasks.vue
@@ -417,7 +417,7 @@ const router = useRouter()
 const store = useProjectStore()
 const { t } = useI18n()
 const { confirmState, confirmDialog, answerConfirm } = useConfirm()
-// WK-13:快捷操作失败 toast 反馈(对齐 Projects.vue useToast 模式)
+// 快捷操作失败 toast 反馈(对齐 Projects.vue useToast 模式)
 const { toast, showToast } = useToast()
 
 // 列表筛选/分页状态持久化到 localStorage(key 前缀 'tasks.'),
@@ -447,14 +447,14 @@ function toggleStatus(key: string) {
 const sortBy = usePersistedRef('tasks.sortBy', 'updated_at')
 const searchKeyword = usePersistedRef('tasks.searchKeyword', '')
 const loading = ref(false)
-// WK-11:搜索态(keyword 过滤可能截断父子关系,子进度徽章/迷你条语义失真 → 隐藏)
+// 搜索态(keyword 过滤可能截断父子关系,子进度徽章/迷你条语义失真 → 隐藏)
 const isSearching = computed(() => searchKeyword.value.trim().length > 0)
 
 // F-260805 D5:列表页一次性加载 + 前端组装树,移除真分页(page/pageSize/totalTasks 已删)。
 // 分组折叠状态(localStorage 记忆)
 const collapsedGroups = reactive(new Set())
 
-// F-260805:父任务展开/折叠状态(localStorage 记忆,沿用 collapsedGroups 模式)
+// 父任务展开/折叠状态(localStorage 记忆,沿用 collapsedGroups 模式)
 const expandedParents = reactive(new Set())
 function toggleParent(id: string) {
   if (expandedParents.has(id)) {
@@ -491,7 +491,7 @@ const STATUS_FILTER_META: Record = {
 const quickStatuses = computed(() =>
   TASK_STATUS_ORDER.map(key => ({ key, icon: STATUS_FILTER_META[key].icon, label: t(STATUS_FILTER_META[key].labelKey) })),
 )
-/** WK-8:快捷状态目标 = 状态机合法流转目标(TASK_STATUS_TRANSITIONS 过滤,对齐 TaskDetail ADVANCE_MAP) */
+/** 快捷状态目标 = 状态机合法流转目标(TASK_STATUS_TRANSITIONS 过滤,对齐 TaskDetail ADVANCE_MAP) */
 function quickStatusesFor(task: TaskRecord): { key: string; icon: string; label: string }[] {
   const valid = new Set(TASK_STATUS_TRANSITIONS[task.status] ?? [])
   return quickStatuses.value.filter(s => valid.has(s.key))
@@ -508,14 +508,14 @@ async function quickAdvance(id: string, target: string) {
     await taskApi.advance(id, target)
     await store.loadTasks(buildTaskQuery())
   } catch (e: any) {
-    // WK-13:快捷操作失败 toast 反馈(原 console.error 静默,用户无感知)
+    // 快捷操作失败 toast 反馈(原 console.error 静默,用户无感知)
     showToast(e?.toString() ?? t('common.unknownError'), 'error')
   }
 }
 async function quickPriority(id: string, priority: number) {
   quickMenuId.value = null
   try {
-    // WK-13:改用 taskApi 直调(不走 store.updateTask,避免失败置 store.error 触发整表错误态)
+    // 改用 taskApi 直调(不走 store.updateTask,避免失败置 store.error 触发整表错误态)
     await taskApi.update(id, 'priority', String(priority))
     await store.loadTasks(buildTaskQuery())
   } catch (e: any) {
@@ -524,13 +524,13 @@ async function quickPriority(id: string, priority: number) {
 }
 async function quickDelete(task: TaskRecord, childCount = 0) {
   quickMenuId.value = null
-  // F-260805:父任务带子任务时确认文案含子任务数(后端级联软删)
+  // 父任务带子任务时确认文案含子任务数(后端级联软删)
   const msg = childCount > 0
     ? t('tasks.confirmDeleteWithChildren', { title: task.title, n: childCount })
     : t('tasks.confirmDelete', { title: task.title })
   if (!await confirmDialog(msg)) return
   try {
-    // WK-13:deleteTask 返成功布尔,失败 toast(store.error 同时走 error-banner)
+    // deleteTask 返成功布尔,失败 toast(store.error 同时走 error-banner)
     const ok = await store.deleteTask(task.id)
     if (!ok) showToast(t('tasks.err.deleteFailed'), 'error')
   } catch (e) { console.error('删除失败:', e) }
@@ -556,7 +556,7 @@ try {
 // 搜索防抖
 let _searchTimer: ReturnType | null = null
 watch(searchKeyword, () => {
-  // WK-10:登记当前关键字到 barrel(数据变更联动刷新复用同一筛选,不冲掉搜索结果)
+  // 登记当前关键字到 barrel(数据变更联动刷新复用同一筛选,不冲掉搜索结果)
   store.setActiveTaskKeyword(searchKeyword.value.trim() || undefined)
   if (_searchTimer) clearTimeout(_searchTimer)
   _searchTimer = setTimeout(() => {
@@ -572,18 +572,18 @@ const newTaskTitle = ref('')
 const newTaskDesc = ref('')
 const newTaskBranch = ref('')
 const newTaskPriority = ref(2)
-// F-260805:父任务下拉选中值(空串=无/顶层任务)
+// 父任务下拉选中值(空串=无/顶层任务)
 const newTaskParentId = ref('')
-// F-260619-01:关联灵感(空串=不关联,与后端 idea_id 空串处理一致)
+// 关联灵感(空串=不关联,与后端 idea_id 空串处理一致)
 const newTaskIdeaId = ref('')
 const submitting = ref(false)
 
-// F-260805:父任务选项 = 当前选中项目的顶层任务(无 parent_id;仅顶层可作父,1 级嵌套)
+// 父任务选项 = 当前选中项目的顶层任务(无 parent_id;仅顶层可作父,1 级嵌套)
 const parentTaskOptions = computed(() =>
   store.tasks.filter(t => !t.parent_id && t.project_id === newTaskProjectId.value),
 )
 
-// F-260805:树形行结构(父任务 + 直接子任务 + 子进度)。1 级嵌套,无递归。
+// 树形行结构(父任务 + 直接子任务 + 子进度)。1 级嵌套,无递归。
 interface TaskRow {
   task: TaskRecord
   /** 父任务的直接子任务(仅父有;叶子为空数组) */
@@ -615,7 +615,7 @@ interface TaskGroup {
   rows: TaskRow[]
 }
 
-// F-260805:树组装 — 顶层 = 无 parent_id 的任务,每个顶层挂 children;父进度前端计算(D6,全量已在前端)。
+// 树组装 — 顶层 = 无 parent_id 的任务,每个顶层挂 children;父进度前端计算(D6,全量已在前端)。
 const taskRows = computed(() => {
   // 子任务按 parent_id 分组(1 级嵌套)
   const childMap = new Map()
@@ -806,7 +806,7 @@ function buildTaskQuery(): TaskQuery | undefined {
   return query
 }
 
-// F-260805:打开新建弹窗。可选 parent(父任务快捷菜单「添加子任务」):
+// 打开新建弹窗。可选 parent(父任务快捷菜单「添加子任务」):
 // 有父 → project_id 锁定为该父所属项目、parent_id 预填;无父(顶部「新建任务」)→ 默认父=无(顶层任务)。
 function openCreateModal(parent?: TaskRecord) {
   if (parent) {
@@ -835,9 +835,9 @@ async function confirmCreate() {
       description: newTaskDesc.value.trim(),
       branch_name: newTaskBranch.value.trim() || undefined,
       priority: newTaskPriority.value,
-      // F-260619-01:空串=不关联(对齐后端 idea_id 空串语义)
+      // 空串=不关联(对齐后端 idea_id 空串语义)
       idea_id: newTaskIdeaId.value.trim() || undefined,
-      // F-260805:父任务透传(空串=顶层任务,后端视为 None)
+      // 父任务透传(空串=顶层任务,后端视为 None)
       parent_id: newTaskParentId.value || undefined,
     })
     if (!r) return
@@ -912,7 +912,7 @@ onMounted(async () => {
       store.loadProjects(),
       // F-260805 D5:一次拉当前筛选全量(limit 500),树形需要完整父子关系
       store.loadTasks(buildTaskQuery()),
-      // F-260619-01:加载灵感供新建任务模态关联下拉选择(幂等,已加载则 no-op)
+      // 加载灵感供新建任务模态关联下拉选择(幂等,已加载则 no-op)
       store.loadIdeas(),
     ])
   } finally {
@@ -1171,7 +1171,7 @@ onUnmounted(() => {
   background: var(--df-bg-card);
   border: 0.5px solid var(--df-border);
   border-radius: var(--df-radius-lg);
-  /* WK-9:原 overflow:hidden 会把快捷菜单(.quick-menu 绝对定位向下溢出)裁切。
+  /* 原 overflow:hidden 会把快捷菜单(.quick-menu 绝对定位向下溢出)裁切。
      改为组自身不裁切,圆角裁剪下放给 header(顶角)与末行(底角,见下),菜单可正常浮出。 */
 }
 
@@ -1184,7 +1184,7 @@ onUnmounted(() => {
   user-select: none;
   border-bottom: 0.5px solid var(--df-border);
   transition: background 0.1s;
-  /* WK-9:承接原 .task-group overflow:hidden 的顶角圆角裁剪(自身 overflow,组不再裁切快捷菜单) */
+  /* 承接原 .task-group overflow:hidden 的顶角圆角裁剪(自身 overflow,组不再裁切快捷菜单) */
   border-radius: var(--df-radius-lg) var(--df-radius-lg) 0 0;
   overflow: hidden;
 }
@@ -1216,7 +1216,7 @@ onUnmounted(() => {
   cursor: pointer;
 }
 .task-item:last-child { border-bottom: none; }
-/* WK-9:末行承接组底角圆角裁剪(替代原 .task-group overflow:hidden) */
+/* 末行承接组底角圆角裁剪(替代原 .task-group overflow:hidden) */
 .task-item:last-child { border-radius: 0 0 var(--df-radius-lg) var(--df-radius-lg); }
 .task-item:hover { background: var(--df-accent-bg); }