优化: 清理无意义注释(走查编号前缀/过时历史标注/死代码注释)
This commit is contained in:
@@ -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<u8> 写字节
|
||||
let encoding = args.get("encoding").and_then(|v| v.as_str()).unwrap_or("utf-8");
|
||||
let bytes: Vec<u8> = 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<FileGrepHit> = 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<String
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// read_file/read_symbol NotFound 提示辅助 — 相近文件名候选(AC-5)
|
||||
// read_file/read_symbol NotFound 提示辅助 — 相近文件名候选
|
||||
// ============================================================
|
||||
|
||||
/// 父目录下与 needle 文件名最相近的 max 个文件名(Dice 字符多重集,思路同 similar_line_fragments)。
|
||||
///
|
||||
/// 用途:read_file/read_symbol 打开文件 NotFound 时,把父目录下最可能的文件名候选附进错误提示,
|
||||
/// 治 LLM 拼错文件名/少写扩展名后盲目重试(AC-5)。低频错误路径(文件不存在),同步 std::fs::read_dir
|
||||
/// 治 LLM 拼错文件名/少写扩展名后盲目重试。低频错误路径(文件不存在),同步 std::fs::read_dir
|
||||
/// 枚举可接受(目录通常不大)。阈值 40% 防误导(完全无关文件名不提示);父目录不可读/非目录返空。
|
||||
fn similar_file_names(dir: &std::path::Path, needle: &str, max: usize) -> Vec<String> {
|
||||
if needle.is_empty() {
|
||||
@@ -1445,7 +1445,7 @@ fn similar_file_names(dir: &std::path::Path, needle: &str, max: usize) -> Vec<St
|
||||
scored.into_iter().map(|(_, s)| s).collect()
|
||||
}
|
||||
|
||||
/// 文件打开 NotFound 的统一错误文案:基础提示 + 父目录相近文件名候选(AC-5)。
|
||||
/// 文件打开 NotFound 的统一错误文案:基础提示 + 父目录相近文件名候选。
|
||||
///
|
||||
/// read_file / read_symbol 共用(DRY)。保留原「建议用 list_directory」引导;候选为空不追加。
|
||||
fn file_not_found_error(path: &str) -> anyhow::Error {
|
||||
|
||||
Reference in New Issue
Block a user