Compare commits

..
3 Commits
5 changed files with 401 additions and 16 deletions

No files matched your search

Generated
+188 -16
View File
@@ -344,6 +344,12 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.2"
@@ -409,6 +415,16 @@ dependencies = [
"syn 3.0.6",
]
[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"serde_core",
]
[[package]]
name = "built"
version = "0.8.1"
@@ -970,7 +986,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"objc2",
]
@@ -1172,6 +1188,16 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.13"
@@ -1235,6 +1261,15 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "futures"
version = "0.3.34"
@@ -1394,7 +1429,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"libc",
"libgit2-sys",
"log",
@@ -1403,6 +1438,19 @@ dependencies = [
"url",
]
[[package]]
name = "globset"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "h2"
version = "0.4.19"
@@ -1763,6 +1811,22 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "ignore"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f"
dependencies = [
"crossbeam-deque",
"globset",
"log",
"memchr",
"regex-automata",
"same-file",
"walkdir",
"winapi-util",
]
[[package]]
name = "image"
version = "0.25.10"
@@ -1828,6 +1892,35 @@ dependencies = [
"web-time",
]
[[package]]
name = "inotify"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d"
dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "interpolate_name"
version = "0.2.4"
@@ -1896,6 +1989,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kqueue"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
dependencies = [
"bitflags 2.13.2",
"libc",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -2114,7 +2227,9 @@ dependencies = [
"fastembed",
"futures-util",
"git2",
"ignore",
"image",
"notify",
"redb",
"reqwest 0.13.5",
"rmcp",
@@ -2239,6 +2354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.61.2",
]
@@ -2359,6 +2475,34 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "notify"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
dependencies = [
"bitflags 2.13.2",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"notify-types",
"walkdir",
"windows-sys 0.52.0",
]
[[package]]
name = "notify-types"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
dependencies = [
"instant",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@@ -2467,7 +2611,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"objc2",
"objc2-core-graphics",
"objc2-foundation",
@@ -2479,7 +2623,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"dispatch2",
"objc2",
]
@@ -2490,7 +2634,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"dispatch2",
"objc2",
"objc2-core-foundation",
@@ -2509,7 +2653,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"objc2",
"objc2-core-foundation",
]
@@ -2520,7 +2664,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"objc2",
"objc2-core-foundation",
]
@@ -2549,7 +2693,7 @@ version = "6.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"libc",
"once_cell",
"onig_sys",
@@ -2571,7 +2715,7 @@ version = "0.10.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"cfg-if",
"foreign-types",
"libc",
@@ -2742,7 +2886,7 @@ version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"crc32fast",
"fdeflate",
"flate2",
@@ -2993,7 +3137,7 @@ version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [
"bitflags",
"bitflags 2.13.2",
]
[[package]]
@@ -3054,7 +3198,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
"bitflags 2.13.2",
]
[[package]]
@@ -3289,7 +3433,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"errno",
"libc",
"linux-raw-sys",
@@ -3356,6 +3500,15 @@ dependencies = [
"tempfile",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.29"
@@ -3403,7 +3556,7 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
@@ -3697,7 +3850,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"core-foundation 0.9.4",
"system-configuration-sys",
]
@@ -4106,7 +4259,7 @@ version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"bitflags",
"bitflags 2.13.2",
"bytes",
"futures-util",
"http",
@@ -4521,6 +4674,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
@@ -4686,6 +4849,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
+2
View File
@@ -37,6 +37,8 @@ tree-sitter-java = "0.23.5"
tree-sitter-c = "0.24.2"
tree-sitter-cpp = "0.23.4"
tree-sitter-go = "0.25.0"
ignore = "0.4.33"
notify = "7"
[build-dependencies]
chrono = "0.4.45"
+134
View File
@@ -0,0 +1,134 @@
use crate::state::MemoryState;
use crate::models::Snippet;
use crate::embedding::generate_embeddings_async;
use ignore::WalkBuilder;
use std::sync::Arc;
use tree_sitter::{Node, Parser};
pub async fn start_background_indexer(state: Arc<MemoryState>) {
// Determine workspace root. Since mcp-memory is usually run from its project root,
// current_dir is a good default for the git repository.
let workspace_root = std::env::current_dir().unwrap_or_else(|_| state.base_dir.clone());
tokio::spawn(async move {
tracing::info!("Starting background indexer in {:?}", workspace_root);
let walker = WalkBuilder::new(&workspace_root)
.hidden(true)
.git_ignore(true)
.build();
let mut files_to_process = Vec::new();
for result in walker {
match result {
Ok(entry) => {
if entry.file_type().map_or(false, |ft| ft.is_file()) {
let path = entry.path().to_path_buf();
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if ["rs", "ts", "js", "jsx", "tsx", "py", "java", "c", "cpp", "go"].contains(&ext) {
files_to_process.push(path);
}
}
}
Err(e) => {
tracing::warn!("Error walking directory: {}", e);
}
}
}
let idx = state.get_search_index();
for file_path in files_to_process {
if let Ok(content) = std::fs::read_to_string(&file_path) {
let ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
let language = match ext {
"rs" => tree_sitter_rust::LANGUAGE,
"ts" | "tsx" | "js" | "jsx" => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
"py" => tree_sitter_python::LANGUAGE,
"java" => tree_sitter_java::LANGUAGE,
"c" | "h" => tree_sitter_c::LANGUAGE,
"cpp" | "cc" | "cxx" | "hpp" | "hxx" => tree_sitter_cpp::LANGUAGE,
"go" => tree_sitter_go::LANGUAGE,
_ => continue,
};
let mut parser = Parser::new();
if parser.set_language(&language.into()).is_err() {
continue;
}
if let Some(tree) = parser.parse(&content, None) {
let mut chunks = Vec::new();
extract_chunks(tree.root_node(), &content, &mut chunks, ext);
for (name, code, desc) in chunks {
// Generate embedding
if let Ok(mut emb) = generate_embeddings_async(vec![code.clone()]).await {
let embedding = emb.pop();
let file_name = file_path.file_name().unwrap_or_default().to_string_lossy();
let snippet_name = format!("{}:{}", file_name, name);
let snippet = Snippet {
name: snippet_name.to_string(),
language: ext.to_string(),
code: code.clone(),
description: format!("{} in {}", desc, file_path.display()),
updated_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
embedding,
};
state.snippets.modify(|snippets| {
// Prevent duplicates if already indexed
if !snippets.iter().any(|s| s.name == snippet.name) {
snippets.push(snippet.clone());
}
});
let _ = idx.index_snippet(&snippet).await;
}
}
}
}
}
tracing::info!("Background indexing completed.");
});
}
fn extract_chunks(node: Node, code: &str, chunks: &mut Vec<(String, String, String)>, ext: &str) {
let kind = node.kind();
let is_structural = matches!(
kind,
"function_item"
| "function_declaration"
| "function_definition"
| "method_definition"
| "struct_item"
| "class_declaration"
);
if is_structural {
let code_text = node.utf8_text(code.as_bytes()).unwrap_or("").to_string();
let mut name = "unknown".to_string();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
let child_kind = child.kind();
if child_kind == "identifier" || child_kind == "name" || child_kind == "type_identifier" {
name = child.utf8_text(code.as_bytes()).unwrap_or("unknown").to_string();
break;
}
}
let desc = format!("{} AST node", kind);
chunks.push((name, code_text, desc));
} else {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
extract_chunks(child, code, chunks, ext);
}
}
}
+7
View File
@@ -12,10 +12,12 @@ mod models;
mod router;
mod search;
pub mod embedding;
pub mod indexer;
mod state;
mod store;
mod tools;
mod clipboard_watcher;
mod watcher;
use crate::api::rest::GateSetReq;
use crate::router::MemoryHandler;
@@ -131,9 +133,14 @@ async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::E
state_for_index.rebuild_index().await;
tracing::info!("Index rebuild complete.");
});
// Start the global codebase indexer
crate::indexer::start_background_indexer(Arc::clone(&state)).await;
tokio::spawn(index_committer_worker(Arc::clone(&state)));
tokio::spawn(ttl_sweeper_worker(Arc::clone(&state)));
crate::clipboard_watcher::spawn_watcher(Arc::clone(&state));
crate::watcher::spawn_watcher(Arc::clone(&state));
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
let app_state = Arc::new(AppState {
handler: Arc::new(MemoryHandler::new(Arc::clone(&state))),
+70
View File
@@ -0,0 +1,70 @@
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::Path;
use std::sync::Arc;
use crate::state::MemoryState;
use tracing::{info, error};
pub fn spawn_watcher(_state: Arc<MemoryState>) {
let watch_path = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
info!("Spawning proactive daemon watcher on {:?}", watch_path);
tokio::spawn(async move {
let (tx, mut rx) = tokio::sync::mpsc::channel::<notify::Result<Event>>(100);
let mut watcher = match RecommendedWatcher::new(
move |res| {
let _ = tx.blocking_send(res);
},
Config::default(),
) {
Ok(w) => w,
Err(e) => {
error!("Failed to create watcher: {}", e);
return;
}
};
if let Err(e) = watcher.watch(&watch_path, RecursiveMode::Recursive) {
error!("Failed to watch path: {}", e);
return;
}
while let Some(res) = rx.recv().await {
match res {
Ok(event) => {
if event.kind.is_modify() {
for path in event.paths {
if should_review(&path) {
info!("Proactive Daemon Hooks: File modified: {:?}", path);
trigger_autonomous_review(&path).await;
}
}
}
}
Err(e) => error!("Watch error: {}", e),
}
}
});
}
fn should_review(path: &Path) -> bool {
let path_str = path.to_string_lossy();
if path_str.contains(".git") || path_str.contains("target") {
return false;
}
if let Some(ext) = path.extension() {
let ext_str = ext.to_string_lossy();
if ext_str == "rs" || ext_str == "md" || ext_str == "toml" || ext_str == "lua" {
return true;
}
}
false
}
async fn trigger_autonomous_review(path: &Path) {
info!("Triggering autonomous review for {:?}", path);
// Simulate invoking a Neovim Lua diagnostic via mcp-memory-win-nvim daemon
// MVP: Just write an info log if Neovim isn't reachable.
info!("Autonomous review complete for {:?}", path);
}