Compare commits

...
4 Commits
10 changed files with 623 additions and 33 deletions

No files matched your search

Generated
+411
View File
@@ -259,6 +259,29 @@ dependencies = [
"arrayvec",
]
[[package]]
name = "aws-lc-rs"
version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
"pkg-config",
]
[[package]]
name = "axum"
version = "0.8.9"
@@ -494,6 +517,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
name = "cfg_aliases"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
[[package]]
name = "chacha20"
version = "0.10.2"
@@ -568,6 +597,15 @@ dependencies = [
"error-code",
]
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@@ -580,6 +618,16 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "combine"
version = "4.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "compact_str"
version = "0.9.1"
@@ -1016,6 +1064,12 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "dyn-clone"
version = "1.0.20"
@@ -1261,6 +1315,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "fsevent-sys"
version = "4.1.0"
@@ -1385,8 +1445,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@@ -1408,9 +1470,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi 6.0.0",
"rand_core 0.10.1",
"wasm-bindgen",
]
[[package]]
@@ -1636,6 +1700,19 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
"hyper",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
@@ -1968,6 +2045,55 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jni"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
"cfg-if",
"combine",
"jni-macros",
"jni-sys",
"log",
"simd_cesu8",
"thiserror",
"walkdir",
"windows-link",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn 2.0.119",
]
[[package]]
name = "jni-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
dependencies = [
"jni-sys-macros",
]
[[package]]
name = "jni-sys-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [
"quote",
"syn 2.0.119",
]
[[package]]
name = "jobserver"
version = "0.1.35"
@@ -2149,6 +2275,12 @@ dependencies = [
"hashbrown 0.16.1",
]
[[package]]
name = "lru-slab"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"
[[package]]
name = "lz4_flex"
version = "0.13.1"
@@ -2230,6 +2362,7 @@ dependencies = [
"ignore",
"image",
"notify",
"qdrant-client",
"redb",
"reqwest 0.13.5",
"rmcp",
@@ -2868,6 +3001,26 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -2970,6 +3123,38 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "prost"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-derive"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "prost-types"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a"
dependencies = [
"prost",
]
[[package]]
name = "pulp"
version = "0.22.3"
@@ -2999,6 +3184,29 @@ version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
name = "qdrant-client"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dddc19df129bad7346ebd027288621ab1ac7e52678371f906b9a8622d7aaf87e"
dependencies = [
"anyhow",
"derive_builder",
"futures",
"futures-util",
"parking_lot",
"prost",
"prost-types",
"reqwest 0.13.5",
"semver",
"serde",
"serde_json",
"thiserror",
"tokio",
"tonic",
"tonic-prost",
]
[[package]]
name = "qoi"
version = "0.4.1"
@@ -3014,6 +3222,63 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quinn"
version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2",
"thiserror",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe"
dependencies = [
"aws-lc-rs",
"bytes",
"getrandom 0.4.3",
"lru-slab",
"rand 0.10.3",
"rand_pcg",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.61.2",
]
[[package]]
name = "quote"
version = "1.0.47"
@@ -3081,6 +3346,15 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "rand_pcg"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "rav1e"
version = "0.8.1"
@@ -3313,19 +3587,26 @@ dependencies = [
"bytes",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@@ -3427,6 +3708,15 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.5"
@@ -3446,6 +3736,7 @@ version = "0.23.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"ring",
@@ -3455,21 +3746,62 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
"openssl-probe 0.2.1",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"web-time",
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98"
dependencies = [
"core-foundation 0.10.1",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f"
[[package]]
name = "rustls-webpki"
version = "0.103.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -3573,6 +3905,12 @@ dependencies = [
"libc",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.229"
@@ -3703,6 +4041,16 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "simd_cesu8"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]]
name = "simd_helpers"
version = "0.1.0"
@@ -4109,6 +4457,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokenizers"
version = "0.23.2"
@@ -4190,6 +4544,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.29.0"
@@ -4237,6 +4602,49 @@ dependencies = [
"serde",
]
[[package]]
name = "tonic"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
dependencies = [
"async-trait",
"axum",
"base64 0.22.1",
"bytes",
"flate2",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-timeout",
"hyper-util",
"percent-encoding",
"pin-project",
"rustls-native-certs",
"socket2",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tokio-stream",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tonic-prost"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
dependencies = [
"bytes",
"prost",
"tonic",
]
[[package]]
name = "tower"
version = "0.5.3"
@@ -4245,9 +4653,12 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"indexmap",
"pin-project-lite",
"slab",
"sync_wrapper",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
+19 -1
View File
@@ -43,7 +43,25 @@ Keep your workflow entirely within your editor rather than copy-pasting code blo
* **Do say:** "Push this refactor to my active Neovim buffer as ghost text so I can review it in-line."
* **Tool Triggered:** `nvim_set_preview` (via `win-nvim` or `linux-nvim`)
## 8. Graph, Memory & Decay Management
## 8. Structural AST Editing
When asking the LLM to modify complex files, prevent indentation bugs and regex failures by guiding it to use tree-sitter.
* **Don't say:** "Search for `fn process()` and replace it with this string."
* **Do say:** "Use the AST node replacer to swap out the `process` function in `server.rs`."
* **Tool Triggered:** `replace_ast_node`
## 9. Interactive Terminal Execution
When asking the LLM to run commands that have prompts, long output, or ANSI colors.
* **Don't say:** "Run `cargo run` and tell me what the output is." (Hides output, hangs on prompts).
* **Do say:** "Send the `cargo run` command to my Neovim terminal split so I can interact with it."
* **Tool Triggered:** `nvim_send_to_terminal`
## 10. Bird's-Eye Repository Exploration
When the LLM is first analyzing a repository, don't let it run `ls -R` and guess.
* **Don't say:** "List the files in the directory and guess where the database code is."
* **Do say:** "Read the directory architecture to get a summary of what each file is responsible for."
* **Tool Triggered:** `read_directory_architecture`
## 11. Graph, Memory & Decay Management
Actively instruct the LLM to maintain its own memory constraints and organize data. The daemon automatically consolidates old sticky notes and snippets to prevent unbounded context growth.
* **Do say:** "Log this architectural decision in the knowledge graph."
* **Do say:** "Add a sticky note with the test database credentials for this session."
+1 -1
View File
@@ -161,7 +161,7 @@ ead_file_skeleton)**: Uses ree-sitter to parse large code files (Rust, Python,
- **Clipboard Watch Mode ( oggle_clipboard_watch_mode)**: Background daemon thread that auto-ingests your Ctrl+C clipboard activity directly into Knowledge Graph StickyNotes while you debug.
- **Ghost Text Previews (
vim_set_preview)**: Pushes proposed LLM code diffs directly into Neovim buffers as ephemeral virtual text.
- [Prompting Guide & Effective Discourse](./PROMPTING_GUIDE.md): Learn how to phrase prompts to get the most out of the agent and memory server.
- [Prompting Guide & Effective Discourse](./EFFECTIVE_DISCOURSE.md): Learn how to phrase prompts to get the most out of the agent and memory server.
## T2R & Token Efficiency Enhancements (V2)
- **AST Node Replacer (
+9 -2
View File
@@ -115,8 +115,15 @@ To maintain absolute traceability, we link memory items directly to the exact gi
The project contains two MCP binaries (win-nvim and linux-nvim) that bridge JSON-RPC over stdio directly to the active Neovim instance.
- **Interactive UI (UDP):** The active editor connects to the backend via UDP (port 3002). The UI script (`gemini-ui.lua`) supports non-destructive **Ghost Text Diff Reviews** and native `vim.ui` prompts without spawning subprocesses. You can trigger these interactively using the backend server.
- **Tools:** These binaries expose basic tools (`nvim_get_cursor`, `nvim_get_active_buffer`, `nvim_list_buffers`, etc.).
- **Interactive Execution QoL:** Use `nvim_send_to_terminal` to run background terminal commands (like tests or builds) in a visible Neovim split so the user can interact with prompts and see ANSI colors.
- **God Mode**: They also expose `nvim_execute_lua`. This is the ultimate fallback tool. If you need to access *any* Neovim API that does not have a dedicated Rust tool (e.g., getting LSP diagnostics, evaluating a visual selection block based on modes, setting registers), you MUST write a short Lua script and pass it to `nvim_execute_lua`. Do not attempt to recompile the Rust server to add new basic tools; use the Lua escape hatch dynamically.
## 16. Tool Schema Discovery (Lazy Loading)
Antigravity automatically caches all MCP tool schemas to your disk to save tokens. Do **NOT** grep or search the Rust source code to find tool schemas or arguments. To understand a tool`s arguments, directly read `~/.gemini/antigravity-cli/mcp/<server_name>/<tool_name>.json`. Do NOT guess arguments. ALWAYS read the schema if you are unfamiliar with a tool to prevent invalid argument errors.
## 16. Structural Code & Architecture Exploration
To reduce token costs and eliminate exact-match string failures:
- **`read_directory_architecture`**: When exploring a new repository, use this to get a bird's-eye view of a directory structure with summaries of file responsibilities, instead of blindly reading files.
- **`replace_ast_node`**: Instead of string-matching `replace_file_content` (which fails on whitespace), use this to precisely replace functions or structs based on AST boundaries.
- **`semantic_code_search`**: Use conceptual vector search instead of raw regex (`grep`) when you need to locate abstract logic (e.g., "Where is authentication handled?").
## 17. Tool Schema Discovery (Lazy Loading)
Antigravity automatically caches all MCP tool schemas to your disk to save tokens. Do **NOT** grep or search the Rust source code to find tool schemas or arguments. To understand a tool's arguments, directly read `~/.gemini/antigravity-cli/mcp/<server_name>/<tool_name>.json`. Do NOT guess arguments. ALWAYS read the schema if you are unfamiliar with a tool to prevent invalid argument errors.
+1 -1
View File
@@ -1121,7 +1121,7 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
},
{
"name": "nvim_send_to_terminal",
"description": "Send a given command string to a Neovim split terminal.",
"description": "Send a given command string to a visible Neovim split terminal. Use this instead of running background commands when you need to see ANSI colors or interact with command prompts.",
"inputSchema": {
"type": "object",
"properties": {
+2 -1
View File
@@ -18,7 +18,7 @@ tantivy = "0.26.1"
tokio = { version = "1.53.1", features = ["full"] }
tracing = "0.1.44"
tracing-subscriber = "0.3.23"
uuid = { version = "1.26.0", features = ["v4"] }
uuid = { version = "1.26.0", features = ["v4", "v5"] }
tracing-appender = "0.2.5"
rmcp = { version = "3.4.0", features = ["server"] }
thiserror = "2.0.20"
@@ -39,6 +39,7 @@ tree-sitter-cpp = "0.23.4"
tree-sitter-go = "0.25.0"
ignore = "0.4.33"
notify = "7"
qdrant-client = "1.19.0"
[build-dependencies]
chrono = "0.4.45"
+37 -24
View File
@@ -618,41 +618,54 @@ impl McpTool for SemanticCodeSearchHandler {
let query_emb = generate_embedding_async(tool_args.query.clone()).await?;
// For MVP, we search across snippets dynamically. A true background codebase indexer would be a separate subsystem.
let mut results = Vec::new();
let mut texts_to_embed = Vec::new();
let mut metadata = Vec::new();
let snippets = state.snippets.read_with(|snips| snips.clone());
for snippet in snippets {
let combined = format!("{} {} {}", snippet.name, snippet.description, snippet.code);
texts_to_embed.push(combined);
metadata.push((snippet.name, snippet.description));
}
let sticky = state.sticky.read_with(|s| s.clone());
for note in sticky {
texts_to_embed.push(note.content.clone());
metadata.push(("StickyNote".to_string(), note.content.chars().take(200).collect::<String>()));
}
if let Ok(embeddings) = generate_embeddings_async(texts_to_embed).await {
for (emb, meta) in embeddings.into_iter().zip(metadata) {
let sim = cosine_similarity(&query_emb, &emb);
results.push((sim, meta.0, meta.1));
// Search using VectorDB if available
let mut vdb_search = false;
if let Some(vdb) = &*state.vector_db.read().await {
vdb_search = true;
if let Ok(search_results) = vdb.search(query_emb.clone(), 5).await {
for res in search_results {
results.push((res.score, res.id, res.text));
}
}
}
results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
// Fallback to manual loop if VectorDB is not initialized
if !vdb_search {
let mut texts_to_embed = Vec::new();
let mut metadata = Vec::new();
let top_results: Vec<_> = results.into_iter().take(5).collect();
let snippets = state.snippets.read_with(|snips| snips.clone());
for snippet in snippets {
let combined = format!("{} {} {}", snippet.name, snippet.description, snippet.code);
texts_to_embed.push(combined);
metadata.push((snippet.name, snippet.description));
}
if top_results.is_empty() {
let sticky = state.sticky.read_with(|s| s.clone());
for note in sticky {
texts_to_embed.push(note.content.clone());
metadata.push(("StickyNote".to_string(), note.content.chars().take(200).collect::<String>()));
}
if let Ok(embeddings) = generate_embeddings_async(texts_to_embed).await {
for (emb, meta) in embeddings.into_iter().zip(metadata) {
let sim = cosine_similarity(&query_emb, &emb);
results.push((sim, meta.0, meta.1));
}
}
results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
results.truncate(5);
}
if results.is_empty() {
return Ok(format!("No semantic matches found for query: {}", tool_args.query));
}
let mut out = format!("Semantic Search Results for '{}':\n", tool_args.query);
for (score, title, desc) in top_results {
for (score, title, desc) in results {
out.push_str(&format!("- [{:.2}] {}: {}\n", score, title, desc));
}
+13
View File
@@ -13,6 +13,7 @@ mod router;
mod search;
pub mod embedding;
pub mod indexer;
pub mod vector_db;
mod state;
mod store;
mod tools;
@@ -545,6 +546,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
rt.block_on(async {
let state = Arc::new(MemoryState::new(&base.to_string_lossy()));
// Initialize Qdrant VectorDB (default local URL)
match crate::vector_db::VectorDB::new("http://localhost:6334", "mcp_memory").await {
Ok(vdb) => {
tracing::info!("Successfully connected to Qdrant vector database");
*state.vector_db.write().await = Some(vdb);
}
Err(e) => {
tracing::warn!("Failed to initialize Qdrant vector database: {}. Vector search will fallback to manual embedding loop. (Is Qdrant running on localhost:6334?)", e);
}
}
if let Err(e) = run_server(state).await {
tracing::error!("Server error: {}", e);
}
+3 -3
View File
@@ -679,7 +679,7 @@ pub struct ToggleClipboardWatchModeTool {
pub struct ReadFileSkeletonTool {
pub file_path: String,
}
/// Replace a specific AST node in a file (robust structural editing)
/// Replace a specific AST node in a file (robust structural editing). Use this instead of regex or line-based string replacement to prevent indentation bugs and matching failures.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReplaceAstNodeTool {
pub file_path: String,
@@ -688,14 +688,14 @@ pub struct ReplaceAstNodeTool {
pub new_content: String,
}
/// Semantic code search using local vector embeddings
/// Semantic code search using local vector embeddings. Use this conceptual search instead of raw regex (grep) when trying to locate abstract logic or exploring new patterns.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SemanticCodeSearchTool {
pub query: String,
pub directory: Option<String>,
}
/// Get a bird's-eye view of directory architecture
/// Get a bird's-eye view of directory architecture. Use this when first exploring a new repository to get a summary of what each file is responsible for, instead of blindly reading files.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadDirectoryArchitectureTool {
pub directory: String,
+127
View File
@@ -0,0 +1,127 @@
use qdrant_client::qdrant::{CreateCollectionBuilder, Distance, PointStruct, VectorParamsBuilder, UpsertPointsBuilder};
use qdrant_client::Qdrant;
use std::sync::Arc;
use std::error::Error;
use uuid::Uuid;
use tracing::{info, error};
use serde::{Deserialize, Serialize};
#[derive(Clone)]
pub struct VectorDB {
client: Arc<Qdrant>,
collection_name: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VectorSearchResult {
pub id: String,
pub doc_type: String,
pub text: String,
pub score: f32,
}
impl VectorDB {
pub async fn new(url: &str, collection_name: &str) -> Result<Self, Box<dyn Error + Send + Sync>> {
let client = Qdrant::from_url(url).build()?;
let db = Self {
client: Arc::new(client),
collection_name: collection_name.to_string(),
};
db.init_collection().await?;
Ok(db)
}
async fn init_collection(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
// Fastembed AllMiniLML6V2 uses 384 dimensions
let vector_params = VectorParamsBuilder::new(384, Distance::Cosine).build();
let collection_exists = self.client.collection_exists(&self.collection_name).await?;
if !collection_exists {
self.client
.create_collection(
CreateCollectionBuilder::new(&self.collection_name)
.vectors_config(vector_params)
)
.await?;
info!("Created Qdrant collection: {}", self.collection_name);
} else {
info!("Qdrant collection {} already exists", self.collection_name);
}
Ok(())
}
pub async fn index_document(
&self,
id: &str,
doc_type: &str,
text: &str,
vector: Vec<f32>,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let point_id = match Uuid::parse_str(id) {
Ok(u) => u.to_string(),
Err(_) => {
// If it's not a valid UUID, let's create a deterministic UUID based on the string
let namespace = Uuid::NAMESPACE_OID;
Uuid::new_v5(&namespace, id.as_bytes()).to_string()
}
};
let mut payload: std::collections::HashMap<String, serde_json::Value> = std::collections::HashMap::new();
payload.insert("doc_type".to_string(), serde_json::Value::String(doc_type.to_string()));
payload.insert("text".to_string(), serde_json::Value::String(text.to_string()));
payload.insert("original_id".to_string(), serde_json::Value::String(id.to_string()));
let point = PointStruct::new(point_id, vector, payload);
self.client
.upsert_points(UpsertPointsBuilder::new(&self.collection_name, vec![point]))
.await?;
Ok(())
}
pub async fn search(
&self,
query_vector: Vec<f32>,
limit: u64,
) -> Result<Vec<VectorSearchResult>, Box<dyn Error + Send + Sync>> {
use qdrant_client::qdrant::SearchPointsBuilder;
let search_result = self.client
.search_points(
SearchPointsBuilder::new(&self.collection_name, query_vector, limit)
.with_payload(true)
)
.await?;
let mut results = Vec::new();
for point in search_result.result {
let id = point.payload.get("original_id")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or_default();
let doc_type = point.payload.get("doc_type")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or_default();
let text = point.payload.get("text")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or_default();
results.push(VectorSearchResult {
id,
doc_type,
text,
score: point.score,
});
}
Ok(results)
}
}