refactor: consolidate nvim crates, extract server library, and update workspace dependencies

This commit is contained in:
Riz Ashraf committed 2026-10-04 01:42:59 +01:00
1 parent a083719cf1
commit 533adfd41b
53 files changed
+5967 -1230

No files matched your search

+348 -3
View File
@@ -123,6 +123,7 @@ impl McpTool for StoreSnippetHandler {
code: req.code,
description: req.description,
updated_at: crate::handlers::utils::now_secs(),
tags: req.tags.unwrap_or_default(),
embedding,
};
@@ -138,6 +139,43 @@ impl McpTool for StoreSnippetHandler {
}
}
pub struct TagSnippetHandler;
#[async_trait]
impl McpTool for TagSnippetHandler {
fn name(&self) -> &'static str {
"tag_snippet"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<TagSnippetTool>("tag_snippet", "Execute tag_snippet")
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: TagSnippetTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut found = false;
state.code.snippets.modify(|snippets| {
for s in snippets.iter_mut() {
if s.name == req.name {
for tag in &req.tags {
if !s.tags.contains(tag) {
s.tags.push(tag.clone());
}
}
found = true;
break;
}
}
});
if found {
Ok(format!("Snippet '{}' tagged with: {:?}", req.name, req.tags))
} else {
Err(crate::error::AppError::Internal(format!("Snippet '{}' not found.", req.name)))
}
}
}
pub struct SearchSnippetsHandler;
#[async_trait]
@@ -147,22 +185,49 @@ impl McpTool for SearchSnippetsHandler {
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<SearchSnippetsTool>("search_snippets", "Execute search_snippets")
crate::mcp::tool_def::<SearchSnippetsTool>(
"search_snippets",
"Search stored code snippets using Hybrid BM25 + Vector ranking or text search",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: SearchSnippetsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let query = req.query;
let limit = req.limit.unwrap_or(10);
let include_body = req.include_body.unwrap_or(false);
let tags_filter = req.tags.unwrap_or_default();
let data = state.code.snippets.read_with(|snippets| {
let results: Vec<_> = snippets
.iter()
.filter(|s| {
contains_ignore_ascii_case(&s.name, &query)
let matches_text = query.is_empty()
|| contains_ignore_ascii_case(&s.name, &query)
|| contains_ignore_ascii_case(&s.description, &query)
|| contains_ignore_ascii_case(&s.language, &query)
|| s.tags.iter().any(|t| contains_ignore_ascii_case(t, &query));
let matches_tags = tags_filter.is_empty()
|| tags_filter.iter().any(|tf| s.tags.iter().any(|st| contains_ignore_ascii_case(st, tf)));
matches_text && matches_tags
})
.take(limit)
.map(|s| {
if include_body {
serde_json::json!(s)
} else {
serde_json::json!({
"name": s.name,
"description": s.description,
"language": s.language,
"tags": s.tags
})
}
})
.collect();
Ok::<String, crate::error::AppError>(serde_json::to_string(&results)?)
Ok::<String, crate::error::AppError>(serde_json::to_string_pretty(&results)?)
})?;
Ok(data)
}
@@ -523,7 +588,60 @@ mod tests {
.unwrap();
assert_eq!(res8, "PR checklist cleared");
}
#[tokio::test]
async fn test_workspace_and_subagent_handlers() {
let temp_dir = tempfile::tempdir().unwrap();
let state = Arc::new(MemoryState::new(temp_dir.path().to_str().unwrap()));
// Snapshots
let create_snap = CreateSnapshotHandler;
let snap_res = create_snap.execute(json!({"description": "Test snap", "namespace": "global"}), state.clone()).await.unwrap();
assert!(snap_res.contains("SNAP-"));
let snap_id = snap_res.split_whitespace().nth(1).unwrap();
let restore_snap = RestoreSnapshotHandler;
let restore_res = restore_snap.execute(json!({"snapshot_id": snap_id}), state.clone()).await.unwrap();
assert!(restore_res.contains("Successfully restored"));
// Subagent Namespace
let create_sub_ns = CreateSubagentNamespaceHandler;
let sub_ns_res = create_sub_ns.execute(json!({"subagent_id": "test_agent"}), state.clone()).await.unwrap();
assert!(sub_ns_res.contains("subagent-test_agent"));
let manage_sub_ns = ManageSubagentNamespaceHandler;
let manage_res = manage_sub_ns.execute(json!({
"subagent_id": "test_agent",
"action": "create"
}), state.clone()).await.unwrap();
assert!(manage_res.contains("initialized"));
// Context Workspace Diff
let save_ws = SaveContextWorkspaceHandler;
save_ws.execute(json!({"name": "ws_a", "description": "Workspace A", "pinned_files": ["src/lib.rs"], "active_task_ids": []}), state.clone()).await.unwrap();
save_ws.execute(json!({"name": "ws_b", "description": "Workspace B", "pinned_files": ["src/main.rs"], "active_task_ids": []}), state.clone()).await.unwrap();
let diff_ws = DiffContextWorkspacesHandler;
let diff_res = diff_ws.execute(json!({"workspace_a": "ws_a", "workspace_b": "ws_b"}), state.clone()).await.unwrap();
assert!(diff_res.contains("workspace_a"));
}
#[tokio::test]
async fn test_diff_context_workspaces_handler() {
let dir = tempfile::tempdir().unwrap();
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
let save_ws = SaveContextWorkspaceHandler;
save_ws.execute(json!({"name": "ws_1", "description": "Workspace 1", "pinned_files": ["src/lib.rs"], "active_task_ids": []}), state.clone()).await.unwrap();
save_ws.execute(json!({"name": "ws_2", "description": "Workspace 2", "pinned_files": ["src/main.rs"], "active_task_ids": []}), state.clone()).await.unwrap();
let diff_ws = DiffContextWorkspacesHandler;
let diff_res = diff_ws.execute(json!({"workspace_a": "ws_1", "workspace_b": "ws_2"}), state.clone()).await.unwrap();
assert!(diff_res.contains("ws_1"));
}
}
use crate::tools::ReadDirectoryArchitectureTool;
use std::fs;
@@ -726,6 +844,95 @@ impl McpTool for RestoreSnapshotHandler {
}
}
pub struct ManageSubagentNamespaceHandler;
#[async_trait]
impl McpTool for ManageSubagentNamespaceHandler {
fn name(&self) -> &'static str {
"manage_subagent_namespace"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<ManageSubagentNamespaceTool>(
"manage_subagent_namespace",
"Manage isolated memory namespaces for subagent sessions (create, condense/promote, or purge)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ManageSubagentNamespaceTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let subagent_ns = format!("subagent-{}", req.subagent_id);
match req.action {
SubagentNamespaceAction::Create => {
state.record_activity("namespace", &format!("Created subagent namespace {}", subagent_ns), None);
Ok(format!("Subagent memory namespace '{}' initialized.", subagent_ns))
}
SubagentNamespaceAction::Purge => {
state.project.pinned_files.modify(|files| {
files.retain(|f| f.namespace != subagent_ns);
});
state.record_activity("namespace", &format!("Purged subagent namespace {}", subagent_ns), None);
Ok(format!("Subagent memory namespace '{}' purged.", subagent_ns))
}
SubagentNamespaceAction::Condense => {
let purge = req.purge_after_promotion.unwrap_or(true);
let mut promoted_entities = 0;
let mut promoted_relations = 0;
state.modify_graph(|g| {
let sub_entities: Vec<Entity> = g
.entities
.values()
.filter(|e| e.namespace == subagent_ns)
.cloned()
.collect();
for mut e in sub_entities {
promoted_entities += 1;
e.namespace = "global".to_string();
let global_entry = g.entities.entry(e.name.clone()).or_insert_with(|| Entity {
name: e.name.clone(),
entity_type: e.entity_type.clone(),
observations: Vec::new(),
namespace: "global".to_string(),
git_branch: e.git_branch.clone(),
});
for obs in e.observations {
if !global_entry.observations.contains(&obs) {
global_entry.observations.push(obs);
}
}
}
for r in &mut g.relations {
if r.namespace == subagent_ns {
promoted_relations += 1;
r.namespace = "global".to_string();
}
}
if purge {
g.entities.retain(|_, e| e.namespace != subagent_ns);
g.relations.retain(|r| r.namespace != subagent_ns);
}
});
if purge {
state.project.pinned_files.modify(|files| {
files.retain(|f| f.namespace != subagent_ns);
});
}
Ok(format!(
"Subagent namespace '{}' condensed: promoted {} entities and {} relations to global graph.",
subagent_ns, promoted_entities, promoted_relations
))
}
}
}
}
pub struct CreateSubagentNamespaceHandler;
#[async_trait]
@@ -770,3 +977,141 @@ impl McpTool for PurgeSubagentNamespaceHandler {
Ok(format!("Subagent memory namespace '{}' purged.", ns))
}
}
pub struct CondenseSubagentNamespaceHandler;
#[async_trait]
impl McpTool for CondenseSubagentNamespaceHandler {
fn name(&self) -> &'static str {
"condense_subagent_namespace"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<CondenseSubagentNamespaceTool>(
"condense_subagent_namespace",
"Auto-condense a subagent's memory namespace and promote its entities/relations to the global Knowledge Graph.",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: CondenseSubagentNamespaceTool =
serde_json::from_value(args).map_err(|e| e.to_string())?;
let subagent_ns = format!("subagent-{}", req.subagent_id);
let purge = req.purge_after_promotion.unwrap_or(true);
let mut promoted_entities = 0;
let mut promoted_relations = 0;
state.modify_graph(|g| {
let sub_entities: Vec<Entity> = g
.entities
.values()
.filter(|e| e.namespace == subagent_ns)
.cloned()
.collect();
for mut e in sub_entities {
promoted_entities += 1;
e.namespace = "global".to_string();
let global_entry = g.entities.entry(e.name.clone()).or_insert_with(|| Entity {
name: e.name.clone(),
entity_type: e.entity_type.clone(),
observations: Vec::new(),
namespace: "global".to_string(),
git_branch: e.git_branch.clone(),
});
for obs in e.observations {
if !global_entry.observations.contains(&obs) {
global_entry.observations.push(obs);
}
}
}
for r in &mut g.relations {
if r.namespace == subagent_ns {
r.namespace = "global".to_string();
promoted_relations += 1;
}
}
if purge {
g.entities.retain(|_, e| e.namespace != subagent_ns);
g.relations.retain(|r| r.namespace != subagent_ns);
}
});
if purge {
state.project.pinned_files.modify(|files| {
files.retain(|f| f.namespace != subagent_ns);
});
}
Ok(format!(
"Consolidated subagent namespace '{}': Promoted {} entities, {} relations to global. Purged: {}",
subagent_ns, promoted_entities, promoted_relations, purge
))
}
}
pub struct DiffContextWorkspacesHandler;
#[async_trait]
impl McpTool for DiffContextWorkspacesHandler {
fn name(&self) -> &'static str {
"diff_context_workspaces"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<DiffContextWorkspacesTool>("diff_context_workspaces", "Execute diff_context_workspaces")
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: DiffContextWorkspacesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let workspaces = state.project.context_workspaces.read_with(|ws| ws.clone());
let ws_a = workspaces.iter().find(|w| w.name == req.workspace_a);
let ws_b = workspaces.iter().find(|w| w.name == req.workspace_b);
if ws_a.is_none() || ws_b.is_none() {
return Err(crate::error::AppError::Internal(format!(
"One or both context workspaces not found: '{}' found={}, '{}' found={}",
req.workspace_a, ws_a.is_some(), req.workspace_b, ws_b.is_some()
)));
}
let a = ws_a.unwrap();
let b = ws_b.unwrap();
let set_files_a: std::collections::HashSet<_> = a.pinned_files.iter().cloned().collect();
let set_files_b: std::collections::HashSet<_> = b.pinned_files.iter().cloned().collect();
let added_files: Vec<_> = set_files_b.difference(&set_files_a).cloned().collect();
let removed_files: Vec<_> = set_files_a.difference(&set_files_b).cloned().collect();
let shared_files: Vec<_> = set_files_a.intersection(&set_files_b).cloned().collect();
let set_tasks_a: std::collections::HashSet<_> = a.active_task_ids.iter().cloned().collect();
let set_tasks_b: std::collections::HashSet<_> = b.active_task_ids.iter().cloned().collect();
let added_tasks: Vec<_> = set_tasks_b.difference(&set_tasks_a).cloned().collect();
let removed_tasks: Vec<_> = set_tasks_a.difference(&set_tasks_b).cloned().collect();
let shared_tasks: Vec<_> = set_tasks_a.intersection(&set_tasks_b).cloned().collect();
let diff = serde_json::json!({
"workspace_a": a.name,
"workspace_b": b.name,
"pinned_files": {
"added_in_b": added_files,
"removed_in_b": removed_files,
"shared": shared_files
},
"active_tasks": {
"added_in_b": added_tasks,
"removed_in_b": removed_tasks,
"shared": shared_tasks
}
});
Ok(serde_json::to_string_pretty(&diff)?)
}
}