推进本地索引与 AI changed-files 审计

- 为本地工作区补充 search/backlinks/tags/resource 引用索引与 watcher 单文件刷新
- 在页面设置中增加索引页签,并展示本地反链、标签和 AI changed-files 审计
- 补充本地搜索与本地 AI changed-files 浏览器 smoke,并回填当前优先级 checklist
This commit is contained in:
lix-2026
2026-05-19 10:22:01 +08:00
parent 1b5d6a2a2d
commit fc4a47e597
8 changed files with 968 additions and 42 deletions
@@ -1,3 +1,4 @@
use crate::routes::{local_workspace_id_from_root_uri, refresh_local_search_index_for_path};
use notify::event::ModifyKind;
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use serde_json::{json, Value};
@@ -191,12 +192,20 @@ fn spawn_local_folder_watcher(
continue;
}
for path in event.paths {
if !is_markdown_path(&path) {
if !is_local_search_index_path(&path) {
continue;
}
let Some(relative_path) = relative_path_string(&canonical_root, &path) else {
continue;
};
refresh_local_search_index_for_event(
&canonical_root,
&root_uri_for_task,
&relative_path,
);
if !is_markdown_path(&path) {
continue;
}
let payload = json!({
"sourceKind": "local_folder",
"rootUri": root_uri_for_task,
@@ -215,6 +224,13 @@ fn spawn_local_folder_watcher(
Ok((sender, shutdown_tx))
}
fn refresh_local_search_index_for_event(root: &Path, root_uri: &str, relative_path: &str) {
let Ok(workspace_id) = local_workspace_id_from_root_uri(root_uri) else {
return;
};
let _ = refresh_local_search_index_for_path(root, root_uri, &workspace_id, relative_path);
}
fn canonical_root_uri(root: &Path) -> String {
format!("file://{}", root.display())
}
@@ -235,6 +251,29 @@ fn is_markdown_path(path: &Path) -> bool {
.unwrap_or(false)
}
fn is_local_search_index_path(path: &Path) -> bool {
if is_markdown_path(path) {
return true;
}
let file_name = path
.file_name()
.and_then(|value| value.to_str())
.unwrap_or_default()
.to_lowercase();
if file_name.ends_with(".mindmap.json") {
return true;
}
let extension = path
.extension()
.and_then(|extension| extension.to_str())
.unwrap_or_default()
.to_lowercase();
matches!(
extension.as_str(),
"doc" | "docx" | "odt" | "ppt" | "pptx" | "odp" | "xls" | "xlsx" | "ods"
)
}
fn should_emit_event_kind(kind: &EventKind) -> bool {
match kind {
EventKind::Create(_) | EventKind::Remove(_) => true,
@@ -276,7 +315,10 @@ fn system_time_ms(time: SystemTime) -> u128 {
#[cfg(test)]
mod tests {
use super::{should_emit_event_kind, LocalFolderWatcherRegistry};
use super::{
is_local_search_index_path, refresh_local_search_index_for_event, should_emit_event_kind,
LocalFolderWatcherRegistry,
};
use notify::event::{AccessKind, CreateKind, DataChange, ModifyKind};
use notify::EventKind;
@@ -348,4 +390,42 @@ mod tests {
AccessKind::Read
)));
}
#[test]
fn watcher_index_path_filter_accepts_markdown_and_resources() {
assert!(is_local_search_index_path(&std::path::Path::new(
"docs/page.md"
)));
assert!(is_local_search_index_path(&std::path::Path::new(
"maps/idea.mindmap.json"
)));
assert!(is_local_search_index_path(&std::path::Path::new(
"office/report.xlsx"
)));
assert!(!is_local_search_index_path(&std::path::Path::new(
"docs/image.png"
)));
}
#[test]
fn watcher_event_refreshes_local_search_index_path() {
let root = test_root("search-index-event");
std::fs::create_dir_all(root.join("docs")).expect("create docs");
std::fs::write(root.join("README.md"), "# Home\n").expect("write home");
std::fs::write(
root.join("docs").join("watched.md"),
"---\ntitle: Watched\n---\n# Watched\nWatcherToken\n",
)
.expect("write watched");
let root_uri = format!("file://{}", root.display());
refresh_local_search_index_for_event(&root, &root_uri, "docs/watched.md");
let index_path = root.join(".mnote").join("index").join("search-index.json");
let index = std::fs::read_to_string(&index_path).expect("index exists");
assert!(index.contains("docs/watched.md"));
assert!(index.contains("WatcherToken"));
let _ = std::fs::remove_dir_all(root);
}
}