refactor: Implement unified search abstraction, MemoryState refactoring, error handling, and unit test expansion
This commit is contained in:
1 parent
a34554b7ff
commit
462f65f66d
21 files changed
+425
-535
No files matched your search
@@ -420,13 +420,13 @@ mod tests {
|
||||
"active_task_ids": ["123"]
|
||||
});
|
||||
|
||||
let res = save_handler.execute(args, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let res = save_handler.execute(args, state.clone()).await.unwrap();
|
||||
assert_eq!(res, "Context workspace saved");
|
||||
|
||||
let list_handler = ListContextWorkspacesHandler;
|
||||
let res2 = list_handler
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
.await.unwrap();
|
||||
assert!(res2.contains("wsl-session"));
|
||||
assert!(res2.contains("src/main.rs"));
|
||||
}
|
||||
@@ -446,7 +446,7 @@ mod tests {
|
||||
});
|
||||
let res1 = store_handler
|
||||
.execute(args_snip, state.clone())
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
.await.unwrap();
|
||||
assert_eq!(res1, "Snippet 'init_db' stored.");
|
||||
|
||||
let search_handler = SearchSnippetsHandler;
|
||||
@@ -455,7 +455,7 @@ mod tests {
|
||||
json!({"query": "SELECT", "namespace": "global"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
.await.unwrap();
|
||||
// Skip assertion since it requires index rebuild
|
||||
|
||||
let pr_handler = AddPrChecklistItemHandler;
|
||||
@@ -463,13 +463,13 @@ mod tests {
|
||||
"description": "Check coverage",
|
||||
"namespace": "global"
|
||||
});
|
||||
let res3 = pr_handler.execute(args_pr, state.clone()).await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
let res3 = pr_handler.execute(args_pr, state.clone()).await.unwrap();
|
||||
assert_eq!(res3, "PR checklist item added");
|
||||
|
||||
let get_pr = GetPrChecklistHandler;
|
||||
let res4 = get_pr
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
.await.unwrap();
|
||||
assert!(res4.contains("Check coverage"));
|
||||
|
||||
// Pin lifecycle
|
||||
@@ -479,13 +479,13 @@ mod tests {
|
||||
json!({"file_path": "src/lib.rs", "namespace": "global"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
.await.unwrap();
|
||||
assert_eq!(res5, "File pinned");
|
||||
|
||||
let list_pins = ListPinnedFilesHandler;
|
||||
let res6 = list_pins
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
.await.unwrap();
|
||||
assert!(res6.contains("src/lib.rs"));
|
||||
|
||||
let unpin = UnpinFileHandler;
|
||||
@@ -494,14 +494,14 @@ mod tests {
|
||||
json!({"file_path": "src/lib.rs", "namespace": "global"}),
|
||||
state.clone(),
|
||||
)
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
.await.unwrap();
|
||||
assert_eq!(res7, "File unpinned");
|
||||
|
||||
// Clear PR
|
||||
let clear_pr = ClearPrChecklistHandler;
|
||||
let res8 = clear_pr
|
||||
.execute(json!({"namespace": "global"}), state.clone())
|
||||
.await.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
|
||||
.await.unwrap();
|
||||
assert_eq!(res8, "PR checklist cleared");
|
||||
}
|
||||
}
|
||||
@@ -585,7 +585,6 @@ impl McpTool for ReadDirectoryArchitectureHandler {
|
||||
}
|
||||
}
|
||||
use crate::tools::SemanticCodeSearchTool;
|
||||
use crate::embedding::{generate_embedding_async, generate_embeddings_async, cosine_similarity};
|
||||
|
||||
pub struct SemanticCodeSearchHandler;
|
||||
|
||||
@@ -605,57 +604,15 @@ impl McpTool for SemanticCodeSearchHandler {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let tool_args: SemanticCodeSearchTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let query_emb = generate_embedding_async(tool_args.query.clone()).await?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 snippets = state.code.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.code.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);
|
||||
}
|
||||
let results = state.search().semantic_search(&tool_args.query, None, 5).await?;
|
||||
|
||||
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 results {
|
||||
out.push_str(&format!("- [{:.2}] {}: {}\n", score, title, desc));
|
||||
for res in results {
|
||||
out.push_str(&format!("- [{:.2}] {}: {}\n", res.score, res.title, res.body));
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
|
||||
Reference in new issue
Block a user