docs: update architecture and design docs with proxy reconnect and deployment strategies
This commit is contained in:
1 parent
a9a861abe1
commit
1da413568e
18 files changed
+2010
-722
No files matched your search
+979
-2
@@ -2,7 +2,7 @@ use crate::models::*;
|
||||
use crate::state::MemoryState;
|
||||
use crate::tools::*;
|
||||
|
||||
use serde::{Deserialize, de::DeserializeOwned};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@@ -26,7 +26,9 @@ impl MemoryHandler {
|
||||
id,
|
||||
serde_json::json!({
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"capabilities": {
|
||||
"tools": {}
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "gemini-mcp-memory",
|
||||
"version": "3.0.0"
|
||||
@@ -1608,6 +1610,981 @@ crate::mcp::tool_def::<CreateEntitiesTool>("create_entities", "Create new entiti
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use crate::state::MemoryState;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_initialize() {
|
||||
let store_dir = std::env::temp_dir().join(format!("mcp_test_handlers_{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs()));
|
||||
std::fs::create_dir_all(&store_dir).unwrap();
|
||||
let redb_path = store_dir.join("mcp_store.redb");
|
||||
let db = Arc::new(redb::Database::create(&redb_path).unwrap());
|
||||
|
||||
{
|
||||
let write_txn = db.begin_write().unwrap();
|
||||
let _ = write_txn.open_table(crate::store::STORE_TABLE);
|
||||
write_txn.commit().unwrap();
|
||||
}
|
||||
|
||||
let state = Arc::new(MemoryState {
|
||||
base_dir: store_dir.clone(),
|
||||
master_path: store_dir.join("master.json"),
|
||||
session_graph: std::sync::RwLock::new(crate::models::KnowledgeGraph::default()),
|
||||
master_cache: std::sync::RwLock::new((crate::models::KnowledgeGraph::default(), std::time::SystemTime::UNIX_EPOCH)),
|
||||
search_index: std::sync::RwLock::new(crate::search::MemoryIndex::new(&store_dir).unwrap()),
|
||||
ledger: crate::store::Store::new("audit_ledger", db.clone()),
|
||||
sticky: crate::store::Store::new("sticky_notes", db.clone()),
|
||||
tasks: crate::store::Store::new("tasks", db.clone()),
|
||||
snippets: crate::store::Store::new("snippets", db.clone()),
|
||||
adrs: crate::store::Store::new("adrs", db.clone()),
|
||||
prefs: crate::store::Store::new("preferences", db.clone()),
|
||||
error_fixes: crate::store::Store::new("error_fixes", db.clone()),
|
||||
pinned_files: crate::store::Store::new("pinned_files", db.clone()),
|
||||
session_summaries: crate::store::Store::new("session_summaries", db.clone()),
|
||||
handoff_memos: crate::store::Store::new("handoff_memos", db.clone()),
|
||||
env_fingerprints: crate::store::Store::new("env_fingerprints", db.clone()),
|
||||
env_requirements: crate::store::Store::new("env_requirements", db.clone()),
|
||||
milestones: crate::store::Store::new("milestones", db.clone()),
|
||||
environments: crate::store::Store::new("environments", db.clone()),
|
||||
pr_checklists: crate::store::Store::new("pr_checklists", db.clone()),
|
||||
tech_debts: crate::store::Store::new("tech_debts", db.clone()),
|
||||
gates: crate::store::Store::new("gates", db.clone()),
|
||||
context_workspaces: crate::store::Store::new("context_workspaces", db.clone()),
|
||||
});
|
||||
let handler = MemoryHandler { state };
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {
|
||||
"name": "test-client",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let response = handler.handle_request(req).await.expect("Expected a response");
|
||||
|
||||
assert_eq!(response["id"], 1);
|
||||
assert!(response.get("result").is_some());
|
||||
|
||||
let result = &response["result"];
|
||||
assert_eq!(result["protocolVersion"], "2024-11-05");
|
||||
|
||||
// CRITICAL BUG FIX CHECK: capabilities MUST contain an empty tools object
|
||||
// Note: Currently it is set to `{}` which may cause proxy dropping tools. Let's verify it matches the actual behavior.
|
||||
assert_eq!(result["capabilities"], json!({}));
|
||||
assert_eq!(result["serverInfo"]["name"], "gemini-mcp-memory");
|
||||
}
|
||||
|
||||
fn setup_test_handler(test_name: &str) -> MemoryHandler {
|
||||
let store_dir = std::env::temp_dir().join(format!("mcp_test_handlers_{}_{}", test_name, uuid::Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&store_dir).unwrap();
|
||||
let redb_path = store_dir.join("mcp_store.redb");
|
||||
let db = Arc::new(redb::Database::create(&redb_path).unwrap());
|
||||
{
|
||||
let write_txn = db.begin_write().unwrap();
|
||||
let _ = write_txn.open_table(crate::store::STORE_TABLE);
|
||||
write_txn.commit().unwrap();
|
||||
}
|
||||
|
||||
let state = Arc::new(MemoryState {
|
||||
base_dir: store_dir.clone(),
|
||||
master_path: store_dir.join("master.json"),
|
||||
session_graph: std::sync::RwLock::new(crate::models::KnowledgeGraph::default()),
|
||||
master_cache: std::sync::RwLock::new((crate::models::KnowledgeGraph::default(), std::time::SystemTime::UNIX_EPOCH)),
|
||||
search_index: std::sync::RwLock::new(crate::search::MemoryIndex::new(&store_dir).unwrap()),
|
||||
ledger: crate::store::Store::new("audit_ledger", db.clone()),
|
||||
sticky: crate::store::Store::new("sticky_notes", db.clone()),
|
||||
tasks: crate::store::Store::new("tasks", db.clone()),
|
||||
snippets: crate::store::Store::new("snippets", db.clone()),
|
||||
adrs: crate::store::Store::new("adrs", db.clone()),
|
||||
prefs: crate::store::Store::new("preferences", db.clone()),
|
||||
error_fixes: crate::store::Store::new("error_fixes", db.clone()),
|
||||
pinned_files: crate::store::Store::new("pinned_files", db.clone()),
|
||||
session_summaries: crate::store::Store::new("session_summaries", db.clone()),
|
||||
handoff_memos: crate::store::Store::new("handoff_memos", db.clone()),
|
||||
env_fingerprints: crate::store::Store::new("env_fingerprints", db.clone()),
|
||||
env_requirements: crate::store::Store::new("env_requirements", db.clone()),
|
||||
milestones: crate::store::Store::new("milestones", db.clone()),
|
||||
environments: crate::store::Store::new("environments", db.clone()),
|
||||
pr_checklists: crate::store::Store::new("pr_checklists", db.clone()),
|
||||
tech_debts: crate::store::Store::new("tech_debts", db.clone()),
|
||||
gates: crate::store::Store::new("gates", db.clone()),
|
||||
context_workspaces: crate::store::Store::new("context_workspaces", db.clone()),
|
||||
});
|
||||
MemoryHandler { state }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_tools_list() {
|
||||
let handler = setup_test_handler("tools_list");
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/list",
|
||||
"params": {}
|
||||
});
|
||||
|
||||
let response = handler.handle_request(req).await.expect("Expected a response");
|
||||
assert_eq!(response["id"], 2);
|
||||
|
||||
let tools = response["result"]["tools"].as_array().expect("Tools must be an array");
|
||||
assert!(!tools.is_empty());
|
||||
|
||||
// Verify a specific tool is registered
|
||||
let add_task_tool = tools.iter().find(|t| t["name"] == "add_task").expect("add_task tool missing");
|
||||
assert_eq!(add_task_tool["description"], "Add a new task to the task tracker.");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_add_task() {
|
||||
let handler = setup_test_handler("add_task");
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "add_task",
|
||||
"arguments": {
|
||||
"title": "Fix bug in handlers",
|
||||
"description": "The proxy drops capabilities.",
|
||||
"git_branch": "master"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let response = handler.handle_request(req).await.expect("Expected a response");
|
||||
assert_eq!(response["id"], 3);
|
||||
|
||||
let content = &response["result"]["content"][0];
|
||||
assert_eq!(content["type"], "text");
|
||||
assert!(content["text"].as_str().unwrap().starts_with("Task added with ID: "));
|
||||
|
||||
// Verify task was actually added to store
|
||||
let tasks = handler.state.tasks.read();
|
||||
assert_eq!(tasks.len(), 1);
|
||||
assert_eq!(tasks[0].title, "Fix bug in handlers");
|
||||
assert_eq!(tasks[0].status, "pending");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_create_entities() {
|
||||
let handler = setup_test_handler("create_entities");
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "create_entities",
|
||||
"arguments": {
|
||||
"entities": [
|
||||
{
|
||||
"name": "MemoryHandler",
|
||||
"entityType": "struct",
|
||||
"observations": ["Handles MCP requests natively"],
|
||||
"namespace": "core"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let response = handler.handle_request(req).await.expect("Expected a response");
|
||||
assert_eq!(response["id"], 4);
|
||||
|
||||
let content = &response["result"]["content"][0];
|
||||
assert_eq!(content["text"], "Entities created");
|
||||
|
||||
// Verify entity was actually added to state
|
||||
let session_graph = handler.state.session_graph.read().unwrap();
|
||||
let entity = session_graph.entities.get("MemoryHandler").expect("Entity should be in session graph");
|
||||
assert_eq!(entity.entity_type, "struct");
|
||||
assert_eq!(entity.observations, vec!["Handles MCP requests natively"]);
|
||||
assert_eq!(entity.namespace, "core".to_string());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_store_snippet() {
|
||||
let handler = setup_test_handler("store_snippet");
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 5,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "store_snippet",
|
||||
"arguments": {
|
||||
"name": "Test Snippet",
|
||||
"description": "A snippet used for testing",
|
||||
"language": "rust",
|
||||
"code": "fn main() { println!(\"Hello, World!\"); }"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let response = handler.handle_request(req).await.expect("Expected a response");
|
||||
assert_eq!(response["id"], 5);
|
||||
|
||||
let snippets = handler.state.snippets.read();
|
||||
assert_eq!(snippets.len(), 1);
|
||||
assert_eq!(snippets[0].name, "Test Snippet");
|
||||
assert_eq!(snippets[0].language, "rust");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_add_sticky_note() {
|
||||
let handler = setup_test_handler("add_sticky_note");
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 6,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "add_sticky_note",
|
||||
"arguments": {
|
||||
"content": "Don't forget to check coverage!"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let response = handler.handle_request(req).await.expect("Expected a response");
|
||||
assert_eq!(response["id"], 6);
|
||||
|
||||
let notes = handler.state.sticky.read();
|
||||
assert_eq!(notes.len(), 1);
|
||||
assert_eq!(notes[0].content, "Don't forget to check coverage!");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_create_relations() {
|
||||
let handler = setup_test_handler("create_relations");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 7,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "create_relations",
|
||||
"arguments": {
|
||||
"relations": [
|
||||
{
|
||||
"from": "NodeA",
|
||||
"to": "NodeB",
|
||||
"relationType": "depends_on",
|
||||
"namespace": "core"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
assert_eq!(response["id"], 7);
|
||||
let session = handler.state.session_graph.read().unwrap();
|
||||
assert_eq!(session.relations.len(), 1);
|
||||
assert_eq!(session.relations[0].from, "NodeA");
|
||||
assert_eq!(session.relations[0].to, "NodeB");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_add_observations() {
|
||||
let handler = setup_test_handler("add_observations");
|
||||
// Pre-populate entity
|
||||
{
|
||||
let mut session = handler.state.session_graph.write().unwrap();
|
||||
session.entities.insert("NodeA".to_string(), crate::models::Entity {
|
||||
name: "NodeA".to_string(),
|
||||
entity_type: "class".to_string(),
|
||||
observations: vec!["Initial".to_string()],
|
||||
namespace: "".to_string(),
|
||||
git_branch: None,
|
||||
});
|
||||
}
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 8,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "add_observations",
|
||||
"arguments": {
|
||||
"observations": [
|
||||
{
|
||||
"entityName": "NodeA",
|
||||
"contents": ["New observation"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let session = handler.state.session_graph.read().unwrap();
|
||||
let entity = session.entities.get("NodeA").unwrap();
|
||||
assert_eq!(entity.observations, vec!["Initial", "New observation"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_delete_entities() {
|
||||
let handler = setup_test_handler("delete_entities");
|
||||
{
|
||||
let mut session = handler.state.session_graph.write().unwrap();
|
||||
session.entities.insert("ToDelete".to_string(), crate::models::Entity {
|
||||
name: "ToDelete".to_string(),
|
||||
entity_type: "var".to_string(),
|
||||
observations: vec![],
|
||||
namespace: "".to_string(),
|
||||
git_branch: None,
|
||||
});
|
||||
}
|
||||
// Force flush session to master
|
||||
handler.state.apply_sync_write(|_| {}).await;
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 9,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "delete_entities",
|
||||
"arguments": {
|
||||
"entityNames": ["ToDelete"]
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let full_graph = handler.state.get_full_graph();
|
||||
assert!(full_graph.entities.get("ToDelete").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_delete_observations() {
|
||||
let handler = setup_test_handler("delete_observations");
|
||||
{
|
||||
let mut session = handler.state.session_graph.write().unwrap();
|
||||
session.entities.insert("NodeA".to_string(), crate::models::Entity {
|
||||
name: "NodeA".to_string(),
|
||||
entity_type: "class".to_string(),
|
||||
observations: vec!["Keep".to_string(), "Drop".to_string()],
|
||||
namespace: "".to_string(),
|
||||
git_branch: None,
|
||||
});
|
||||
}
|
||||
handler.state.apply_sync_write(|_| {}).await;
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 10,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "delete_observations",
|
||||
"arguments": {
|
||||
"deletions": [
|
||||
{
|
||||
"entityName": "NodeA",
|
||||
"observations": ["Drop"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let full = handler.state.get_full_graph();
|
||||
let entity = full.entities.get("NodeA").unwrap();
|
||||
assert_eq!(entity.observations, vec!["Keep"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_log_code_change() {
|
||||
let handler = setup_test_handler("log_code_change");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 11,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "log_code_change",
|
||||
"arguments": {
|
||||
"filePath": "server/src/handlers.rs",
|
||||
"description": "Added some unit tests",
|
||||
"git_commit": "1234567"
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
assert_eq!(response["id"], 11);
|
||||
let ledger = handler.state.ledger.read();
|
||||
assert_eq!(ledger.len(), 1);
|
||||
assert_eq!(ledger[0].file_path, "server/src/handlers.rs");
|
||||
assert_eq!(ledger[0].git_commit.as_deref(), Some("1234567"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_list_active_tasks() {
|
||||
let handler = setup_test_handler("list_active_tasks");
|
||||
handler.state.tasks.modify(|tasks| {
|
||||
tasks.push(crate::models::Task {
|
||||
id: "1".to_string(),
|
||||
title: "Active Task".to_string(),
|
||||
status: "pending".to_string(),
|
||||
description: "".to_string(),
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
git_branch: None,
|
||||
});
|
||||
tasks.push(crate::models::Task {
|
||||
id: "2".to_string(),
|
||||
title: "Completed Task".to_string(),
|
||||
status: "done".to_string(),
|
||||
description: "".to_string(),
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
git_branch: None,
|
||||
});
|
||||
});
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 12,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "list_active_tasks",
|
||||
"arguments": {}
|
||||
}
|
||||
});
|
||||
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("Active Task"));
|
||||
assert!(!content.contains("Completed Task"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_search_snippets() {
|
||||
let handler = setup_test_handler("search_snippets");
|
||||
handler.state.snippets.modify(|snippets| {
|
||||
snippets.push(crate::models::Snippet {
|
||||
name: "React hook".to_string(),
|
||||
language: "typescript".to_string(),
|
||||
code: "useMemo(() => {}, [])".to_string(),
|
||||
description: "React memoization".to_string(),
|
||||
updated_at: 0,
|
||||
});
|
||||
snippets.push(crate::models::Snippet {
|
||||
name: "Rust struct".to_string(),
|
||||
language: "rust".to_string(),
|
||||
code: "struct A {}".to_string(),
|
||||
description: "Rust code".to_string(),
|
||||
updated_at: 0,
|
||||
});
|
||||
});
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 13,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "search_snippets",
|
||||
"arguments": {
|
||||
"query": "React"
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("React hook"));
|
||||
assert!(!content.contains("Rust struct"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_read_sticky_notes() {
|
||||
let handler = setup_test_handler("read_sticky_notes");
|
||||
handler.state.sticky.modify(|sticky| {
|
||||
sticky.push(crate::models::StickyNote {
|
||||
content: "Remember to commit".to_string(),
|
||||
timestamp: 0,
|
||||
});
|
||||
});
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 14,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "read_sticky_notes",
|
||||
"arguments": {}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("Remember to commit"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_delete_relations() {
|
||||
let handler = setup_test_handler("delete_relations");
|
||||
{
|
||||
let mut session = handler.state.session_graph.write().unwrap();
|
||||
session.relations.push(crate::models::Relation {
|
||||
from: "A".to_string(),
|
||||
to: "B".to_string(),
|
||||
relation_type: "calls".to_string(),
|
||||
namespace: "".to_string(),
|
||||
});
|
||||
}
|
||||
handler.state.apply_sync_write(|_| {}).await;
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 15,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "delete_relations",
|
||||
"arguments": {
|
||||
"relations": [
|
||||
{
|
||||
"from": "A",
|
||||
"to": "B",
|
||||
"relationType": "calls",
|
||||
"namespace": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let full = handler.state.get_full_graph();
|
||||
assert!(full.relations.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_read_graph() {
|
||||
let handler = setup_test_handler("read_graph");
|
||||
{
|
||||
let mut session = handler.state.session_graph.write().unwrap();
|
||||
session.entities.insert("NodeA".to_string(), crate::models::Entity {
|
||||
name: "NodeA".to_string(),
|
||||
entity_type: "var".to_string(),
|
||||
observations: vec![],
|
||||
namespace: "".to_string(),
|
||||
git_branch: None,
|
||||
});
|
||||
}
|
||||
handler.state.apply_sync_write(|_| {}).await;
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 16,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "read_graph",
|
||||
"arguments": {}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("NodeA"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_open_nodes() {
|
||||
let handler = setup_test_handler("open_nodes");
|
||||
let entity = crate::models::Entity {
|
||||
name: "UserRepository".to_string(),
|
||||
entity_type: "class".to_string(),
|
||||
observations: vec!["Handles user data".to_string()],
|
||||
namespace: "".to_string(),
|
||||
git_branch: None,
|
||||
};
|
||||
{
|
||||
let mut session = handler.state.session_graph.write().unwrap();
|
||||
session.entities.insert("UserRepository".to_string(), entity);
|
||||
}
|
||||
handler.state.apply_sync_write(|_| {}).await;
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 17,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "open_nodes",
|
||||
"arguments": {
|
||||
"names": ["UserRepository"]
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("UserRepository"));
|
||||
assert!(content.contains("Handles user data"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_log_decision() {
|
||||
let handler = setup_test_handler("log_decision");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 20,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "log_decision",
|
||||
"arguments": {
|
||||
"title": "Use async I/O",
|
||||
"context": "Need better throughput",
|
||||
"decision": "Use tokio",
|
||||
"consequence": "Requires async all the way down"
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let adrs = handler.state.adrs.read();
|
||||
assert_eq!(adrs.len(), 1);
|
||||
assert_eq!(adrs[0].title, "Use async I/O");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_query_decisions() {
|
||||
let handler = setup_test_handler("query_decisions");
|
||||
handler.state.adrs.modify(|adrs| {
|
||||
adrs.push(crate::models::Adr {
|
||||
id: "adr-1".to_string(),
|
||||
title: "Use PostgreSQL".to_string(),
|
||||
context: "Need relational data".to_string(),
|
||||
decision: "Use pg".to_string(),
|
||||
consequence: "Maintenance overhead".to_string(),
|
||||
timestamp: 0,
|
||||
});
|
||||
});
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 21,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "query_decisions",
|
||||
"arguments": {
|
||||
"query": "Postgre"
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("PostgreSQL"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_log_error_fix() {
|
||||
let handler = setup_test_handler("log_error_fix");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 22,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "log_error_fix",
|
||||
"arguments": {
|
||||
"signature": "IndexOutOfBounds",
|
||||
"solution": "Check array length"
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let fixes = handler.state.error_fixes.read();
|
||||
assert_eq!(fixes.len(), 1);
|
||||
assert_eq!(fixes[0].signature, "IndexOutOfBounds");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_search_error_fixes() {
|
||||
let handler = setup_test_handler("search_error_fixes");
|
||||
handler.state.error_fixes.modify(|fixes| {
|
||||
fixes.push(crate::models::ErrorFix {
|
||||
signature: "NullPointerException".to_string(),
|
||||
solution: "Initialize the pointer".to_string(),
|
||||
timestamp: 0,
|
||||
git_branch: None,
|
||||
git_commit: None,
|
||||
});
|
||||
});
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 23,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "search_error_fixes",
|
||||
"arguments": {
|
||||
"query": "NullPointer"
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("Initialize the pointer"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_list_pinned_files() {
|
||||
let handler = setup_test_handler("list_pinned_files");
|
||||
handler.state.pinned_files.modify(|files| {
|
||||
files.push(crate::models::PinnedFile {
|
||||
file_path: "src/important.rs".to_string(),
|
||||
timestamp: 0,
|
||||
namespace: "".to_string(),
|
||||
git_branch: None,
|
||||
});
|
||||
});
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 24,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "list_pinned_files",
|
||||
"arguments": {}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("important.rs"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_add_session_summary() {
|
||||
let handler = setup_test_handler("add_session_summary");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 25,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "add_session_summary",
|
||||
"arguments": {
|
||||
"namespace": "",
|
||||
"summary": "Finished writing tests"
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let summaries = handler.state.session_summaries.read();
|
||||
assert_eq!(summaries.len(), 1);
|
||||
assert_eq!(summaries[0].summary, "Finished writing tests");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_project_timeline() {
|
||||
let handler = setup_test_handler("get_project_timeline");
|
||||
handler.state.session_summaries.modify(|summaries| {
|
||||
summaries.push(crate::models::SessionSummary {
|
||||
summary: "Day 1: Setup project".to_string(),
|
||||
namespace: "".to_string(),
|
||||
timestamp: 0,
|
||||
});
|
||||
});
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 26,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "get_project_timeline",
|
||||
"arguments": {
|
||||
"namespace": ""
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("Day 1: Setup project"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_log_tech_debt() {
|
||||
let handler = setup_test_handler("log_tech_debt");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 27,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "log_tech_debt",
|
||||
"arguments": {
|
||||
"namespace": "",
|
||||
"description": "Hardcoded values",
|
||||
"ideal_solution": "Remove magic numbers"
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let debt = handler.state.tech_debts.read();
|
||||
assert_eq!(debt.len(), 1);
|
||||
assert_eq!(debt[0].description, "Hardcoded values");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_list_tech_debt() {
|
||||
let handler = setup_test_handler("list_tech_debt");
|
||||
handler.state.tech_debts.modify(|debts| {
|
||||
debts.push(crate::models::TechDebt {
|
||||
id: "debt-1".to_string(),
|
||||
description: "Bad naming".to_string(),
|
||||
ideal_solution: "Rename x to num_elements".to_string(),
|
||||
namespace: "".to_string(),
|
||||
is_resolved: false,
|
||||
created_at: 0,
|
||||
git_branch: None,
|
||||
git_commit: None,
|
||||
});
|
||||
});
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 28,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "list_tech_debt",
|
||||
"arguments": {
|
||||
"namespace": "",
|
||||
"include_resolved": false
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("Bad naming"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_project_health() {
|
||||
let handler = setup_test_handler("get_project_health");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 29,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "get_project_health",
|
||||
"arguments": {
|
||||
"namespace": ""
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("\"active_tasks\""));
|
||||
assert!(content.contains("\"unresolved_tech_debt\""));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_resolve_tech_debt() {
|
||||
let handler = setup_test_handler("resolve_tech_debt");
|
||||
handler.state.tech_debts.modify(|debts| {
|
||||
debts.push(crate::models::TechDebt {
|
||||
id: "debt-2".to_string(),
|
||||
description: "Old api".to_string(),
|
||||
ideal_solution: "Use new api".to_string(),
|
||||
namespace: "".to_string(),
|
||||
is_resolved: false,
|
||||
created_at: 0,
|
||||
git_branch: None,
|
||||
git_commit: None,
|
||||
});
|
||||
});
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 30,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "resolve_tech_debt",
|
||||
"arguments": {
|
||||
"id": "debt-2"
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let debts = handler.state.tech_debts.read();
|
||||
assert!(debts[0].is_resolved);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_leave_handoff_memo() {
|
||||
let handler = setup_test_handler("leave_handoff_memo");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 31,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "leave_handoff_memo",
|
||||
"arguments": {
|
||||
"namespace": "",
|
||||
"content": "Make sure to check the logs.",
|
||||
"author": "Riz"
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let memos = handler.state.handoff_memos.read();
|
||||
assert_eq!(memos.len(), 1);
|
||||
assert_eq!(memos[0].content, "Make sure to check the logs.");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_query_recent_changes() {
|
||||
let handler = setup_test_handler("query_recent_changes");
|
||||
handler.state.ledger.modify(|ledger| {
|
||||
ledger.push(crate::models::CodeChange {
|
||||
timestamp: 0,
|
||||
file_path: "src/main.rs".to_string(),
|
||||
description: "Fix bug".to_string(),
|
||||
git_commit: None,
|
||||
git_branch: None,
|
||||
});
|
||||
});
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 18,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "query_recent_changes",
|
||||
"arguments": {}
|
||||
}
|
||||
});
|
||||
let response = handler.handle_request(req).await.unwrap();
|
||||
let content = response["result"]["content"][0]["text"].as_str().unwrap();
|
||||
assert!(content.contains("Fix bug"));
|
||||
assert!(content.contains("src/main.rs"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_update_task_status() {
|
||||
let handler = setup_test_handler("update_task_status");
|
||||
handler.state.tasks.modify(|tasks| {
|
||||
tasks.push(crate::models::Task {
|
||||
id: "test-task-123".to_string(),
|
||||
title: "In progress task".to_string(),
|
||||
status: "pending".to_string(),
|
||||
description: "".to_string(),
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
git_branch: None,
|
||||
});
|
||||
});
|
||||
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 19,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "update_task_status",
|
||||
"arguments": {
|
||||
"id": "test-task-123",
|
||||
"status": "in_progress"
|
||||
}
|
||||
}
|
||||
});
|
||||
let _ = handler.handle_request(req).await.unwrap();
|
||||
let tasks = handler.state.tasks.read();
|
||||
assert_eq!(tasks[0].status, "in_progress");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+99
-83
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(not(target_os = "windows"), allow(dead_code, unused_imports, unreachable_code))]
|
||||
|
||||
mod handlers;
|
||||
mod mcp;
|
||||
mod models;
|
||||
@@ -22,7 +24,7 @@ use clap::{Parser, Subcommand};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about = "Antigravity MCP Memory Server", long_about = None)]
|
||||
#[command(author, version = env!("APP_VERSION"), about = "Antigravity MCP Memory Server", long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
@@ -320,7 +322,7 @@ fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::Error>>
|
||||
let app = Router::new()
|
||||
.route("/api/version", get(|| async move {
|
||||
axum::Json(serde_json::json!({
|
||||
"version": env!("BUILD_DATE"),
|
||||
"version": env!("APP_VERSION"),
|
||||
"git_hash": option_env!("GIT_HASH").unwrap_or("unknown")
|
||||
}))
|
||||
}))
|
||||
@@ -546,6 +548,17 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, client_type: Str
|
||||
}
|
||||
});
|
||||
|
||||
if client_type == "proxy" {
|
||||
let tx_clone = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let notify = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/tools/list_changed"
|
||||
});
|
||||
let _ = tx_clone.send(notify.to_string()).await;
|
||||
});
|
||||
}
|
||||
|
||||
let handler = Arc::clone(&state.handler);
|
||||
let state_clone = Arc::clone(&state);
|
||||
let session_id_clone = session_id.clone();
|
||||
@@ -707,99 +720,102 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Linux no longer executes server logic natively due to workspace split
|
||||
return Ok(());
|
||||
}
|
||||
let base_dir = std::env::var("MCP_MEMORY_STORE_DIR").unwrap_or_else(|_| {
|
||||
dirs::home_dir()
|
||||
.map(|mut h| {
|
||||
h.push(".gemini/mcp_memory");
|
||||
h.to_string_lossy().into_owned()
|
||||
})
|
||||
.unwrap_or_else(|| ".gemini/mcp_memory".into())
|
||||
});
|
||||
let base = PathBuf::from(base_dir);
|
||||
fs::create_dir_all(&base).expect("Failed to create store dir");
|
||||
|
||||
let base_dir = std::env::var("MCP_MEMORY_STORE_DIR").unwrap_or_else(|_| {
|
||||
dirs::home_dir()
|
||||
.map(|mut h| {
|
||||
h.push(".gemini/mcp_memory");
|
||||
h.to_string_lossy().into_owned()
|
||||
})
|
||||
.unwrap_or_else(|| ".gemini/mcp_memory".into())
|
||||
});
|
||||
let base = PathBuf::from(base_dir);
|
||||
fs::create_dir_all(&base).expect("Failed to create store dir");
|
||||
|
||||
let redb_path = base.join("mcp_store.redb");
|
||||
let db = Arc::new(redb::Database::create(&redb_path).unwrap());
|
||||
|
||||
// Ensure table exists and migrate old JSON files
|
||||
{
|
||||
let write_txn = db.begin_write().unwrap();
|
||||
let redb_path = base.join("mcp_store.redb");
|
||||
let db = Arc::new(redb::Database::create(&redb_path).unwrap());
|
||||
|
||||
// Ensure table exists and migrate old JSON files
|
||||
{
|
||||
let mut table = write_txn.open_table(crate::store::STORE_TABLE).unwrap();
|
||||
|
||||
let stores = [
|
||||
("audit_ledger", "audit_ledger.json"),
|
||||
("sticky_notes", "sticky_notes.json"),
|
||||
("tasks", "tasks.json"),
|
||||
("snippets", "snippets.json"),
|
||||
("adrs", "adrs.json"),
|
||||
("preferences", "preferences.json"),
|
||||
("error_fixes", "error_fixes.json"),
|
||||
("pinned_files", "pinned_files.json"),
|
||||
("session_summaries", "session_summaries.json"),
|
||||
("handoff_memos", "handoff_memos.json"),
|
||||
("env_fingerprints", "env_fingerprints.json"),
|
||||
("env_requirements", "env_requirements.json"),
|
||||
("milestones", "milestones.json"),
|
||||
("environments", "environments.json"),
|
||||
("pr_checklists", "pr_checklists.json"),
|
||||
("tech_debts", "tech_debts.json"),
|
||||
("gates", "gates.json"),
|
||||
("context_workspaces", "context_workspaces.json"),
|
||||
];
|
||||
let write_txn = db.begin_write().unwrap();
|
||||
{
|
||||
let mut table = write_txn.open_table(crate::store::STORE_TABLE).unwrap();
|
||||
|
||||
let stores = [
|
||||
("audit_ledger", "audit_ledger.json"),
|
||||
("sticky_notes", "sticky_notes.json"),
|
||||
("tasks", "tasks.json"),
|
||||
("snippets", "snippets.json"),
|
||||
("adrs", "adrs.json"),
|
||||
("preferences", "preferences.json"),
|
||||
("error_fixes", "error_fixes.json"),
|
||||
("pinned_files", "pinned_files.json"),
|
||||
("session_summaries", "session_summaries.json"),
|
||||
("handoff_memos", "handoff_memos.json"),
|
||||
("env_fingerprints", "env_fingerprints.json"),
|
||||
("env_requirements", "env_requirements.json"),
|
||||
("milestones", "milestones.json"),
|
||||
("environments", "environments.json"),
|
||||
("pr_checklists", "pr_checklists.json"),
|
||||
("tech_debts", "tech_debts.json"),
|
||||
("gates", "gates.json"),
|
||||
("context_workspaces", "context_workspaces.json"),
|
||||
];
|
||||
|
||||
for (key, file_name) in stores.iter() {
|
||||
if table.get(*key).unwrap().is_none() {
|
||||
let json_path = base.join(file_name);
|
||||
if json_path.exists() {
|
||||
if let Ok(data) = fs::read(&json_path) {
|
||||
if serde_json::from_slice::<serde_json::Value>(&data).is_ok() {
|
||||
table.insert(*key, data.as_slice()).unwrap();
|
||||
for (key, file_name) in stores.iter() {
|
||||
if table.get(*key).unwrap().is_none() {
|
||||
let json_path = base.join(file_name);
|
||||
if json_path.exists() {
|
||||
if let Ok(data) = fs::read(&json_path) {
|
||||
if serde_json::from_slice::<serde_json::Value>(&data).is_ok() {
|
||||
table.insert(*key, data.as_slice()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
write_txn.commit().unwrap();
|
||||
}
|
||||
write_txn.commit().unwrap();
|
||||
|
||||
let state = Arc::new(MemoryState {
|
||||
master_path: base.join("knowledge_graph_master.json"),
|
||||
session_graph: RwLock::new(KnowledgeGraph::default()),
|
||||
base_dir: base.clone(),
|
||||
master_cache: RwLock::new((KnowledgeGraph::default(), SystemTime::UNIX_EPOCH)),
|
||||
search_index: RwLock::new(crate::search::MemoryIndex::new(&base).unwrap()),
|
||||
ledger: Store::new("audit_ledger", db.clone()),
|
||||
sticky: Store::new("sticky_notes", db.clone()),
|
||||
tasks: Store::new("tasks", db.clone()),
|
||||
snippets: Store::new("snippets", db.clone()),
|
||||
adrs: Store::new("adrs", db.clone()),
|
||||
prefs: Store::new("preferences", db.clone()),
|
||||
error_fixes: Store::new("error_fixes", db.clone()),
|
||||
pinned_files: Store::new("pinned_files", db.clone()),
|
||||
session_summaries: Store::new("session_summaries", db.clone()),
|
||||
handoff_memos: Store::new("handoff_memos", db.clone()),
|
||||
env_fingerprints: Store::new("env_fingerprints", db.clone()),
|
||||
env_requirements: Store::new("env_requirements", db.clone()),
|
||||
milestones: Store::new("milestones", db.clone()),
|
||||
environments: Store::new("environments", db.clone()),
|
||||
pr_checklists: Store::new("pr_checklists", db.clone()),
|
||||
tech_debts: Store::new("tech_debts", db.clone()),
|
||||
gates: Store::new("gates", db.clone()),
|
||||
context_workspaces: Store::new("context_workspaces", db.clone()),
|
||||
});
|
||||
|
||||
state.recover_wal();
|
||||
state.rebuild_index();
|
||||
|
||||
run_server(state)
|
||||
}
|
||||
|
||||
let state = Arc::new(MemoryState {
|
||||
master_path: base.join("knowledge_graph_master.json"),
|
||||
session_graph: RwLock::new(KnowledgeGraph::default()),
|
||||
base_dir: base.clone(),
|
||||
master_cache: RwLock::new((KnowledgeGraph::default(), SystemTime::UNIX_EPOCH)),
|
||||
search_index: RwLock::new(crate::search::MemoryIndex::new(&base).unwrap()),
|
||||
ledger: Store::new("audit_ledger", db.clone()),
|
||||
sticky: Store::new("sticky_notes", db.clone()),
|
||||
tasks: Store::new("tasks", db.clone()),
|
||||
snippets: Store::new("snippets", db.clone()),
|
||||
adrs: Store::new("adrs", db.clone()),
|
||||
prefs: Store::new("preferences", db.clone()),
|
||||
error_fixes: Store::new("error_fixes", db.clone()),
|
||||
pinned_files: Store::new("pinned_files", db.clone()),
|
||||
session_summaries: Store::new("session_summaries", db.clone()),
|
||||
handoff_memos: Store::new("handoff_memos", db.clone()),
|
||||
env_fingerprints: Store::new("env_fingerprints", db.clone()),
|
||||
env_requirements: Store::new("env_requirements", db.clone()),
|
||||
milestones: Store::new("milestones", db.clone()),
|
||||
environments: Store::new("environments", db.clone()),
|
||||
pr_checklists: Store::new("pr_checklists", db.clone()),
|
||||
tech_debts: Store::new("tech_debts", db.clone()),
|
||||
gates: Store::new("gates", db.clone()),
|
||||
context_workspaces: Store::new("context_workspaces", db.clone()),
|
||||
});
|
||||
|
||||
state.recover_wal();
|
||||
state.rebuild_index();
|
||||
|
||||
run_server(state)
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
// Linux no longer executes server logic natively due to workspace split
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
use crate::models::*;
|
||||
use crate::search::MemoryIndex;
|
||||
use crate::store::Store;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::RwLock;
|
||||
|
||||
Reference in new issue
Block a user