修复: workspace_root 编译期常量消除+data_dir 运行期+首次授权引导

- 删除 production 中全部 env!("CARGO_MANIFEST_DIR")引用
- workspace_root() 替换为运行期 data_dir(Tauri app_data_dir)
- 相对路径锚定改为 AllowedDirs.first_persistent_dir(),无授权时引导绑定项目
- .trash 迁到 data_dir/.trash,含自动迁移
- authz_debug → temp_dir,workspace_drive → current_dir
- run_command 默认 working_dir 改为空串
- G1 目标刷新改为每次消息覆盖(含 ai_chat_edit 路径)
- #6 评分关键词拆到独立文件 scoring_keywords.rs
- #7 promote 补偿删除 purge_with_descendants → soft_delete
- #8 关联双向同步: 后端事务 sync_related_ids + IPC + 前端全链路
- 设置面板新增数据目录显示
This commit is contained in:
2026-06-27 21:32:12 +08:00
parent a6d692270f
commit 9c3f27f4ca
26 changed files with 743 additions and 104 deletions

5
.cargo/config.toml Normal file
View File

@@ -0,0 +1,5 @@
# musl linux 交叉编译(Windows 主机无 cc/musl-gcc,用 rust-lld self-contained link)
# df-relay 云端部署:本地交叉产 static linux binary → scp 测试机普通运行
[target.x86_64-unknown-linux-musl]
linker = "rust-lld"
rustflags = ["-C", "link-self-contained=y"]

105
.cargo/ts-bench/Cargo.lock generated Normal file
View File

@@ -0,0 +1,105 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "cc"
version = "1.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "regex"
version = "1.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "tree-sitter"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0203df02a3b6dd63575cc1d6e609edc2181c9a11867a271b25cfd2abff3ec5ca"
dependencies = [
"cc",
"regex",
"regex-syntax",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-language"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782"
[[package]]
name = "tree-sitter-rust"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8ccb3e3a3495c8a943f6c3fd24c3804c471fd7f4f16087623c7fa4c0068e8a"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "ts-bench"
version = "0.1.0"
dependencies = [
"tree-sitter",
"tree-sitter-rust",
]

View File

@@ -0,0 +1,13 @@
[package]
name = "ts-bench"
version = "0.1.0"
edition = "2021"
[dependencies]
tree-sitter = "0.23"
tree-sitter-rust = "0.23"
[profile.release]
opt-level = 3
[workspace]

184
.cargo/ts-bench/src/main.rs Normal file
View File

@@ -0,0 +1,184 @@
use std::env;
use std::fs;
use std::hint::black_box;
use std::time::Instant;
use tree_sitter::{Node, Parser, Query, QueryCursor};
fn char_to_token_est(chars: usize) -> usize {
(chars + 3) / 4
}
fn extract_skeleton(root: Node, src: &str, q: &Query) -> (String, usize) {
let mut out = String::new();
let mut count = 0usize;
let mut cursor = QueryCursor::new();
let matches = cursor.matches(q, root, src.as_bytes());
for mat in matches {
for cap in mat.captures {
let n = cap.node;
let start_byte = n.start_byte();
let header_end = {
let from_start = &src[start_byte..];
let mut depth: i32 = 0;
let mut end = from_start.len();
for (i, b) in from_start.char_indices() {
match b {
'{' => {
if depth == 0 {
end = i;
break;
}
depth += 1;
}
';' => {
end = i;
break;
}
_ => {}
}
let _ = depth;
}
start_byte + end.min(from_start.len())
};
let header = &src[start_byte..header_end].trim_end();
out.push_str(header);
out.push_str(" { ... }\n");
count += 1;
}
}
(out, count)
}
fn main() {
let args: Vec<String> = env::args().collect();
let path = args.get(1).expect("usage: ts-bench <file.rs>");
let src = fs::read_to_string(path).expect("read");
let src_chars = src.chars().count();
let src_tokens_est = char_to_token_est(src.len());
let mut parser = Parser::new();
parser.set_language(&tree_sitter_rust::LANGUAGE.into()).unwrap();
// warmup
let _ = parser.parse(&src, None).unwrap();
// ---- PURE PARSE timing ----
let n = 200;
let mut times_us = Vec::with_capacity(n);
for _ in 0..n {
let t0 = Instant::now();
let tree = parser.parse(black_box(&src), None).unwrap();
let t1 = Instant::now();
drop(tree);
times_us.push(t1.duration_since(t0).as_nanos() as f64 / 1000.0);
}
times_us.sort_by(|a, b| a.partial_cmp(b).unwrap());
let min = times_us[0];
let med = times_us[n / 2];
let p95 = times_us[(n as f64 * 0.95) as usize];
let mean = times_us.iter().sum::<f64>() / n as f64;
// ---- INCREMENTAL PARSE (edit one byte at end) ----
let mut src_edited = src.clone();
src_edited.push_str("\n// comment\n");
let tree0 = parser.parse(&src, None).unwrap();
let mut times_inc = Vec::with_capacity(n);
for _ in 0..n {
let t0 = Instant::now();
let t2 = parser.parse(&src_edited, Some(&tree0)).unwrap();
let t1 = Instant::now();
drop(t2);
times_inc.push(t1.duration_since(t0).as_nanos() as f64 / 1000.0);
}
times_inc.sort_by(|a, b| a.partial_cmp(b).unwrap());
let inc_med = times_inc[n / 2];
// ---- Query compilation timing ----
let query_text = r#"
[
(function_item name: (identifier) @name)
(struct_item name: (type_identifier) @name)
(enum_item name: (type_identifier) @name)
(trait_item name: (type_identifier) @name)
(impl_item trait: (type_identifier)? @trait type: (type_identifier)? @type)
(const_item name: (identifier) @name)
(static_item name: (identifier) @name)
(type_item name: (type_identifier) @name)
(mod_item name: (identifier) @name)
(macro_definition name: (identifier) @name)
]
"#;
let mut qtimes = Vec::with_capacity(n);
for _ in 0..n {
let t0 = Instant::now();
let q = Query::new(&tree_sitter_rust::LANGUAGE.into(), query_text).unwrap();
let t1 = Instant::now();
drop(q);
qtimes.push(t1.duration_since(t0).as_nanos() as f64 / 1000.0);
}
qtimes.sort_by(|a, b| a.partial_cmp(b).unwrap());
let q_med = qtimes[n / 2];
// ---- Skeleton extraction (compiled query reused) ----
let tree = parser.parse(&src, None).unwrap();
let root = tree.root_node();
let q = Query::new(&tree_sitter_rust::LANGUAGE.into(), query_text).unwrap();
let m = 200;
let mut ext_us = Vec::with_capacity(m);
let mut last_skel = String::new();
let mut last_count = 0;
for _ in 0..m {
let t0 = Instant::now();
let (skel, cnt) = extract_skeleton(root, &src, &q);
let t1 = Instant::now();
ext_us.push(t1.duration_since(t0).as_nanos() as f64 / 1000.0);
last_skel = skel;
last_count = cnt;
}
ext_us.sort_by(|a, b| a.partial_cmp(b).unwrap());
let ext_med = ext_us[m / 2];
let skel_chars = last_skel.chars().count();
let skel_tokens_est = char_to_token_est(last_skel.len());
println!("=== FILE ===");
println!("path : {}", path);
println!("lines : {}", src.lines().count());
println!("chars : {}", src_chars);
println!("bytes : {}", src.len());
println!("tokens est (~/4c) : {}", src_tokens_est);
println!();
println!("=== PARSE SPEED (pure tree-sitter-rust, release, {} iters) ===", n);
println!("min={:.2}us median={:.2}us p95={:.2}us mean={:.2}us", min, med, p95, mean);
println!("median ms : {:.3} ms", med / 1000.0);
println!();
println!("=== INCREMENTAL PARSE (small edit) ===");
println!("median : {:.2}us ({:.3}ms)", inc_med, inc_med / 1000.0);
println!();
println!("=== QUERY COMPILATION (once, should be cached) ===");
println!("median : {:.2}us ({:.3}ms)", q_med, q_med / 1000.0);
println!();
println!("=== SKELETON EXTRACTION (compiled query reused) ===");
println!("symbols extracted : {}", last_count);
println!("skeleton chars : {}", skel_chars);
println!("skeleton tokens : {}", skel_tokens_est);
println!("extract median : {:.2}us ({:.3}ms)", ext_med, ext_med / 1000.0);
println!();
println!("=== DENSITY (full vs skeleton) ===");
println!(
"chars : {:.1}x ({:.1}% reduction)",
src_chars as f64 / skel_chars as f64,
(1.0 - skel_chars as f64 / src_chars as f64) * 100.0
);
println!(
"tokens : {:.1}x ({:.1}% reduction)",
src_tokens_est as f64 / skel_tokens_est as f64,
(1.0 - skel_tokens_est as f64 / src_tokens_est as f64) * 100.0
);
println!();
println!("=== SKELETON PREVIEW (first 30 lines) ===");
for line in last_skel.lines().take(30) {
println!("{}", line);
}
}