From 4e1a633dbd30d19f69d30ad893a01823d65237c7 Mon Sep 17 00:00:00 2001 From: Riz Ashraf Date: Wed, 30 Sep 2026 20:43:28 +0100 Subject: [PATCH] Refactor: Fix clippy warnings and introduce AppError --- Cargo.lock | 7 ++++ PROMPTING_GUIDE.md | 43 ----------------------- mcp_tools_review.md | 61 +++++++++++++++++++++++++++++++++ server/src/clipboard_watcher.rs | 8 ++--- server/src/error.rs | 18 +++++++++- server/src/handlers/ast.rs | 10 ++---- server/src/handlers/git.rs | 5 ++- server/src/handlers/meta.rs | 4 +-- server/src/handlers/vision.rs | 43 +++++++++-------------- server/src/indexer.rs | 6 ++-- server/src/search.rs | 8 ++--- server/src/state.rs | 17 +++++---- server/src/vector_db.rs | 2 +- 13 files changed, 129 insertions(+), 103 deletions(-) delete mode 100644 PROMPTING_GUIDE.md create mode 100644 mcp_tools_review.md diff --git a/Cargo.lock b/Cargo.lock index 965e6fb..d58bbaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4010,6 +4010,12 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sharded-slab" version = "0.1.7" @@ -5053,6 +5059,7 @@ dependencies = [ "getrandom 0.4.3", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] diff --git a/PROMPTING_GUIDE.md b/PROMPTING_GUIDE.md deleted file mode 100644 index 553701e..0000000 --- a/PROMPTING_GUIDE.md +++ /dev/null @@ -1,43 +0,0 @@ -# Effective Discourse: LLM Prompting Guide for MCP Memory - -To get the most out of the Antigravity MCP Memory server and its advanced developer tools, use specific phrases that clearly state your intent. This guides the LLM to use the most efficient tools, reducing token consumption, speeding up time-to-resolve (T2R), and avoiding brute-force file reading. - -## 1. Codebase Exploration & Token Efficiency -When entering a new file, avoid having the LLM read the entire contents blindly. -* **Don't say:** "Read server.rs and tell me what it does." *(Consumes massive tokens)* -* **Do say:** "Extract the AST skeleton of server.rs to understand its structure first." -* **Tool Triggered:** ead_file_skeleton - -## 2. Debugging & Log Parsing -Stop copy-pasting giant walls of logs into the chat interface. -* **Don't say:** "Here is the error: [paste 500 lines of logs]" -* **Do say:** "The daemon crashed. Fetch the recent logs from daemon.log." or "Watch the process logs for server.log." -* **Tool Triggered:** get_recent_logs, watch_process_logs - -## 3. Git & Context Handoff -When you've been working independently and need to loop the LLM back in on your current state. -* **Don't say:** "I changed some files, here are the diffs..." -* **Do say:** "Get the active git worktree context to review my uncommitted changes before we continue." -* **Tool Triggered:** get_active_worktree_context - -## 4. Clipboard Watch Mode (Research & Triage) -When you are doing intense debugging across StackOverflow, logs, and docs, use the clipboard watcher to auto-ingest your breadcrumbs. -* **Action:** Ask the LLM to turn it on: "Enable clipboard watch mode." -* **Do say:** "I'm going to reproduce the bug and copy some stack traces and IDs. Give me a minute, then read my latest sticky notes to catch up." -* **Tool Triggered:** oggle_clipboard_watch_mode, followed by internal Sticky Note reads. - -## 5. Neovim Ghost Text (Live Previews) -Keep your workflow entirely within your editor rather than copy-pasting code blocks from the chat. -* **Don't say:** "Write the updated function here so I can copy-paste it." -* **Do say:** "Push this refactor to my active Neovim buffer as ghost text so I can review it in-line." -* **Tool Triggered:** -vim_set_preview - -## 6. Graph & Memory Management -Actively instruct the LLM to maintain its own memory constraints and organize data. -* **Do say:** "Log this architectural decision in the knowledge graph." -* **Do say:** "Add a sticky note with the test database credentials for this session." -* **Do say:** "Create a milestone for the 'Rich Clipboard' feature and break it down into active tasks." -* **Tools Triggered:** create_entities, log_decision, dd_sticky_note, dd_milestone, dd_task - -By phrasing requests around *actions* rather than *information retrieval*, the LLM is primed to leverage the rich MCP toolset built into the Antigravity Memory Server. diff --git a/mcp_tools_review.md b/mcp_tools_review.md new file mode 100644 index 0000000..c6340b2 --- /dev/null +++ b/mcp_tools_review.md @@ -0,0 +1,61 @@ +# MCP Tools Review & Enhancement Strategy + +## Part 1: Current Arsenal Review + +Our current MCP ecosystem is highly advanced, utilizing a **Dual-Transport Leader/Stub Architecture** (Windows Host + WSL Proxy) to completely eliminate cross-OS I/O latency. + +### 1. Context & Token Optimization +* ead_file_skeleton: Highly effective. Uses ree-sitter to extract ASTs (Rust, Python, TS). **Score: A+ (Massive token savings)** +* get_active_worktree_context: Native git2 integration. Bypasses shell parsing for clean JSON diffs. **Score: A** +* watch_process_logs / get_recent_logs: Direct file seeking. Prevents LLMs from reading multi-megabyte log files. **Score: A** + +### 2. Neovim IDE Integration ( +vim-core) +* +vim_set_preview, +vim_goto_line, +vim_set_diagnostics, +vim_execute_lua, +vim_get_active_buffer. +* **Review:** Exceptional human QoL. The agent interacts with the code where the human's eyes actually are. Ghost text and diagnostic extmarks provide an IDE-like experience usually reserved for closed-source tools like Cursor. **Score: S-Tier** + +### 3. Clipboard & Workflow +* write_clipboard, ead_clipboard, oggle_clipboard_watch_mode. +* **Review:** Native cross-OS clipboard-win and rboard implementation. Auto-ingesting into StickyNotes bridges the gap between manual human research and the agent's context. **Score: A** + +### 4. Graph & Memory Management +* create_entities, dd_sticky_note, save_context_workspace, handoff_routine. +* **Review:** Solid foundation for state persistence across branches and days. **Score: B+** (Could use more automated TTL/decay for outdated context). + +--- + +## Part 2: Proposed Enhancements (Focus: T2R, Token Cost, QoL) + +To push the system to the absolute bleeding edge of autonomous coding, I propose the following 5 new tools/enhancements. + +### 1. eplace_ast_node (Robust Structural Editing) +* **The Problem:** The current eplace_file_content uses exact string matching and line numbers. Line numbers change when humans edit simultaneously, and string matching fails on whitespace/indentation. +* **The Solution:** An MCP tool that takes (file_path, node_type, node_name, new_content). It uses ree-sitter to find the exact boundary of n execute(...) and replaces just that AST node. +* **Impact:** Zero LLM syntax/indentation errors. 100% robust edits. Drastically lowers Time-to-Resolve (T2R) by eliminating failed edit loops. + +### 2. semantic_code_search (Local Vector Embeddings) +* **The Problem:** grep_search relies on exact regex. If the LLM guesses the wrong variable name, it wastes tokens searching and reading the wrong files. +* **The Solution:** We already have antivy and astembed in our Cargo.toml. We can index the AST blocks of the codebase in the background. The LLM can query *"Where is the auth token validated?"* and get the exact 3 relevant functions instantly. +* **Impact:** Massive token cost reduction (no blind file reading). Instant T2R for codebase exploration. + +### 3. +vim_send_to_terminal (Interactive Execution QoL) +* **The Problem:** When the agent runs a background terminal command (cargo build, +pm run dev), the output is hidden from the human, and interactive prompts cause the background task to hang indefinitely. +* **The Solution:** A tool that opens a Neovim :term split (or uses a mux pane) and sends the command there. +* **Impact:** Massive Human QoL. The human can watch the tests run natively, interact with prompts, see ANSI colors, and press to kill it if it loops. + +### 4. ead_directory_architecture (Bird's-Eye View) +* **The Problem:** ead_file_skeleton works for one file. When entering a new repository, the LLM usually runs ls -R and then has to guess what files do based on their names. +* **The Solution:** A tool that scans a directory structure and uses basic heuristic parsing (or a tiny local embedding lookup) to return a JSON tree of files alongside a 1-sentence summary of what each file is responsible for. +* **Impact:** Immediate holistic context. Eliminates the "exploration phase" token tax. + +### 5. query_database_schema (Introspection) +* **The Problem:** Working with databases usually involves the LLM writing clunky bash scripts to run psql or sqlite3 to view table definitions, which often fail due to missing env vars or wrong dialects. +* **The Solution:** A direct MCP tool that parses the local .env, connects to the database (Postgres/SQLite), and returns a clean Markdown representation of the schema (Tables, Columns, Types, Foreign Keys). +* **Impact:** Prevents hallucinations about database structure. Fixes DB-related bugs significantly faster (T2R). diff --git a/server/src/clipboard_watcher.rs b/server/src/clipboard_watcher.rs index 185efcc..f4441cf 100644 --- a/server/src/clipboard_watcher.rs +++ b/server/src/clipboard_watcher.rs @@ -19,9 +19,9 @@ pub fn spawn_watcher(state: Arc) { continue; } - if let Ok(_clip) = tokio::task::spawn_blocking(|| Clipboard::new_attempts(3)).await.unwrap() { - if let Ok(text) = get_clipboard::(formats::Unicode) { - if text != last_text && !text.trim().is_empty() { + if let Ok(_clip) = tokio::task::spawn_blocking(|| Clipboard::new_attempts(3)).await.unwrap() + && let Ok(text) = get_clipboard::(formats::Unicode) + && text != last_text && !text.trim().is_empty() { last_text = text.clone(); let note = StickyNote { @@ -40,8 +40,6 @@ pub fn spawn_watcher(state: Arc) { // We use rebuild_index to index the new sticky note state.rebuild_index().await; } - } - } } }); } diff --git a/server/src/error.rs b/server/src/error.rs index 75e8656..8b755e1 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -6,6 +6,8 @@ use axum::{ use serde_json::json; use thiserror::Error; +pub type Result = std::result::Result; + #[derive(Error, Debug)] pub enum AppError { #[error("Not Found: {0}")] @@ -19,6 +21,18 @@ pub enum AppError { #[error("Bad Request: {0}")] BadRequest(String), + + #[error("Serialization Error: {0}")] + JsonError(#[from] serde_json::Error), + + #[error("I/O Error: {0}")] + IoError(#[from] std::io::Error), +} + +impl From for String { + fn from(err: AppError) -> String { + err.to_string() + } } impl IntoResponse for AppError { @@ -26,8 +40,10 @@ impl IntoResponse for AppError { let (status, error_message) = match &self { AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()), AppError::Forbidden(msg) => (StatusCode::FORBIDDEN, msg.clone()), - AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()), AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()), + AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()), + AppError::JsonError(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("JSON Error: {}", e)), + AppError::IoError(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("I/O Error: {}", e)), }; let body = Json(json!({ diff --git a/server/src/handlers/ast.rs b/server/src/handlers/ast.rs index c87fdce..b3ef583 100644 --- a/server/src/handlers/ast.rs +++ b/server/src/handlers/ast.rs @@ -55,13 +55,7 @@ impl McpTool for ReadFileSkeletonHandler { fn extract_skeleton(node: Node, code: &str, out: &mut String, depth: usize) { let kind = node.kind(); - let is_structural = match kind { - "use_declaration" | "import_statement" | "import_from_statement" => true, - "struct_item" | "enum_item" | "trait_item" | "impl_item" => true, - "function_item" | "function_declaration" | "function_definition" | "method_definition" => true, - "interface_declaration" | "type_alias_declaration" | "class_declaration" | "class_definition" => true, - _ => false, - }; + let is_structural = matches!(kind, "use_declaration" | "import_statement" | "import_from_statement" | "struct_item" | "enum_item" | "trait_item" | "impl_item" | "function_item" | "function_declaration" | "function_definition" | "method_definition" | "interface_declaration" | "type_alias_declaration" | "class_declaration" | "class_definition"); if is_structural { let indent = " ".repeat(depth); @@ -84,7 +78,7 @@ impl McpTool for ReadFileSkeletonHandler { } out.push_str(&indent); - out.push_str(&signature.trim()); + out.push_str(signature.trim()); out.push('\n'); } else if node.is_named() { let mut cursor = node.walk(); diff --git a/server/src/handlers/git.rs b/server/src/handlers/git.rs index f5dd120..99bcc6c 100644 --- a/server/src/handlers/git.rs +++ b/server/src/handlers/git.rs @@ -27,11 +27,10 @@ impl McpTool for GetActiveWorktreeContextHandler { let repo = git2::Repository::discover(&cwd).map_err(|e| format!("Not in a git repository: {}", e))?; let mut branch_name = String::new(); - if let Ok(head) = repo.head() { - if let Some(name) = head.shorthand() { + if let Ok(head) = repo.head() + && let Some(name) = head.shorthand() { branch_name = name.to_string(); } - } let mut opts = git2::DiffOptions::new(); let mut diff = None; diff --git a/server/src/handlers/meta.rs b/server/src/handlers/meta.rs index aad7c19..9f84a36 100644 --- a/server/src/handlers/meta.rs +++ b/server/src/handlers/meta.rs @@ -409,7 +409,7 @@ impl McpTool for OmniSearchHandler { )); } }; - println!("OMNI SEARCH MATCHES: {:?}", matches); + // tracing::info!("OMNI SEARCH MATCHES: {:?}", matches); let q = req.query.clone(); let query_emb = crate::embedding::generate_embedding_async(q.clone()).await.unwrap_or_default(); @@ -846,7 +846,7 @@ mod tests { .execute(json!({"query": "Omni"}), state.clone()) .await .unwrap(); - println!("OMNI RES: {}", omni_res); + // tracing::info!("OMNI RES: {}", omni_res); assert!( omni_res.contains("omni-1"), "omni search should return results containing the task id" diff --git a/server/src/handlers/vision.rs b/server/src/handlers/vision.rs index 855cada..322c83f 100644 --- a/server/src/handlers/vision.rs +++ b/server/src/handlers/vision.rs @@ -32,25 +32,21 @@ impl McpTool for WriteClipboardHandler { let mut msgs = Vec::new(); // Handle clipboard_win formats (text, html, files) - if tool_args.text.is_some() || tool_args.html.is_some() || tool_args.files.is_some() { - if let Ok(_clip) = Clipboard::new_attempts(3) { - if let Some(text) = &tool_args.text { - if clipboard_win::set_clipboard_string(text).is_ok() { + if (tool_args.text.is_some() || tool_args.html.is_some() || tool_args.files.is_some()) + && let Ok(_clip) = Clipboard::new_attempts(3) { + if let Some(text) = &tool_args.text + && clipboard_win::set_clipboard_string(text).is_ok() { msgs.push("Wrote text"); } - } - if let Some(html) = &tool_args.html { - if formats::Html::new().unwrap().write_clipboard(html).is_ok() { + if let Some(html) = &tool_args.html + && formats::Html::new().unwrap().write_clipboard(html).is_ok() { msgs.push("Wrote HTML"); } - } - if let Some(files) = &tool_args.files { - if raw::set_file_list(files).is_ok() { + if let Some(files) = &tool_args.files + && raw::set_file_list(files).is_ok() { msgs.push("Wrote FileList"); } - } } - } // Handle arboard for image if let Some(image_path) = &tool_args.image_path { @@ -106,26 +102,23 @@ impl McpTool for ReadClipboardHandler { let mut out = serde_json::Map::new(); if let Ok(_clip) = Clipboard::new_attempts(3) { - if let Ok(text) = get_clipboard::(formats::Unicode) { - if !text.trim().is_empty() { + if let Ok(text) = get_clipboard::(formats::Unicode) + && !text.trim().is_empty() { out.insert("text".into(), json!(text)); } - } - if let Ok(html) = get_clipboard::(formats::Html::new().unwrap()) { - if !html.trim().is_empty() { + if let Ok(html) = get_clipboard::(formats::Html::new().unwrap()) + && !html.trim().is_empty() { out.insert("html".into(), json!(html)); } - } - if let Ok(files) = get_clipboard::, _>(formats::FileList) { - if !files.is_empty() { + if let Ok(files) = get_clipboard::, _>(formats::FileList) + && !files.is_empty() { out.insert("files".into(), json!(files)); } - } } - if let Ok(mut clipboard) = arboard::Clipboard::new() { - if let Ok(image_data) = clipboard.get_image() { - if let Some(img) = ImageBuffer::, _>::from_raw( + if let Ok(mut clipboard) = arboard::Clipboard::new() + && let Ok(image_data) = clipboard.get_image() + && let Some(img) = ImageBuffer::, _>::from_raw( image_data.width as u32, image_data.height as u32, image_data.bytes.into_owned(), @@ -152,8 +145,6 @@ impl McpTool for ReadClipboardHandler { out.insert("image_path".into(), json!(file_path.to_string_lossy().to_string())); } } - } - } Ok(Value::Object(out)) }) diff --git a/server/src/indexer.rs b/server/src/indexer.rs index 049445a..8e622d8 100644 --- a/server/src/indexer.rs +++ b/server/src/indexer.rs @@ -22,7 +22,7 @@ pub async fn start_background_indexer(state: Arc) { for result in walker { match result { Ok(entry) => { - if entry.file_type().map_or(false, |ft| ft.is_file()) { + if entry.file_type().is_some_and(|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) { @@ -98,7 +98,7 @@ pub async fn start_background_indexer(state: Arc) { }); } -fn extract_chunks(node: Node, code: &str, chunks: &mut Vec<(String, String, String)>, ext: &str) { +fn extract_chunks(node: Node, code: &str, chunks: &mut Vec<(String, String, String)>, _ext: &str) { let kind = node.kind(); let is_structural = matches!( kind, @@ -128,7 +128,7 @@ fn extract_chunks(node: Node, code: &str, chunks: &mut Vec<(String, String, Stri } else { let mut cursor = node.walk(); for child in node.named_children(&mut cursor) { - extract_chunks(child, code, chunks, ext); + extract_chunks(child, code, chunks, _ext); } } } diff --git a/server/src/search.rs b/server/src/search.rs index 397ada6..4acec81 100644 --- a/server/src/search.rs +++ b/server/src/search.rs @@ -258,7 +258,7 @@ impl MemoryIndex { } pub fn add_task_sync(&self, t: &Task) { - println!("add_task_sync called for task: {}", t.id); + // println!("add_task_sync called for task: {}", t.id); if let Ok(writer) = self.writer.lock() { let _res = writer.add_document(doc!( self.id_field => t.id.as_str(), @@ -267,12 +267,12 @@ impl MemoryIndex { self.type_field => "task", self.namespace_field => "global" )); - println!("Writer add_document returned id/result"); + // println!("Writer add_document returned id/result"); self.needs_commit .store(true, std::sync::atomic::Ordering::SeqCst); - println!("Needs_commit set to true in add_task_sync"); + // println!("Needs_commit set to true in add_task_sync"); } else { - println!("Failed to acquire writer lock in add_task_sync"); + // println!("Failed to acquire writer lock in add_task_sync"); } } diff --git a/server/src/state.rs b/server/src/state.rs index d803dde..9e5706f 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,6 +1,7 @@ use crate::models::*; use crate::search::MemoryIndex; use crate::store::Store; +use crate::vector_db::VectorDB; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{Arc, RwLock}; @@ -17,6 +18,7 @@ pub struct MemoryState { pub clipboard_watch_mode: tokio::sync::RwLock, pub graph: Store, pub search_index: RwLock, + pub vector_db: tokio::sync::RwLock>, pub ledger: Store>, pub sticky: Store>, pub tasks: Store>, @@ -63,6 +65,7 @@ impl MemoryState { std::process::exit(1); } }), + vector_db: tokio::sync::RwLock::new(None), ledger: Store::new("audit_ledger", db.clone()), sticky: Store::new("sticky_notes", db.clone()), tasks: Store::new("tasks", db.clone()), @@ -144,7 +147,7 @@ impl MemoryState { let snippets = self.snippets.read_with(|s| s.clone()); let adrs = self.adrs.read_with(|a| a.clone()); - println!( + tracing::info!( "rebuild_index: found {} entities, {} tasks", entities.len(), tasks.len() @@ -152,7 +155,7 @@ impl MemoryState { let idx_clone = idx.clone(); tokio::task::spawn_blocking(move || { - println!("spawn_blocking started in rebuild_index"); + // tracing::info!("spawn_blocking started in rebuild_index"); for e in entities { idx_clone.add_entity_sync(&e); } @@ -222,13 +225,13 @@ mod tests { let idx = arc_state.search_index.read().unwrap(); // Force reload reader to ensure it sees the commit made by rebuild_index idx.reader.reload().unwrap(); - println!( - "Index reader doc count: {}", - idx.reader.searcher().num_docs() - ); + // tracing::info!( + // "Index reader doc count: {}", + // idx.reader.searcher().num_docs() + // ); let all_docs = idx.search("Test", None).expect("Search failed"); - println!("All docs for 'Test': {:?}", all_docs); + // tracing::info!("All docs for 'Test': {:?}", all_docs); // Verify the task added synchronously is actually searchable let results = idx.search("Test", None).expect("Search failed"); diff --git a/server/src/vector_db.rs b/server/src/vector_db.rs index eee39b2..272a47d 100644 --- a/server/src/vector_db.rs +++ b/server/src/vector_db.rs @@ -3,7 +3,7 @@ use qdrant_client::Qdrant; use std::sync::Arc; use std::error::Error; use uuid::Uuid; -use tracing::{info, error}; +use tracing::info; use serde::{Deserialize, Serialize}; #[derive(Clone)]