perf: fix memory leaks, optimize tracing logs, fix networking eof fragmentation bug in nvim-core and stub, and deduplicate json-rpc handlers
This commit is contained in:
1 parent
ce88bef45e
commit
f76d8ccce8
23 files changed
+1373
-803
No files matched your search
@@ -0,0 +1,504 @@
|
||||
use crate::models::*;
|
||||
use crate::router::McpTool;
|
||||
use crate::state::MemoryState;
|
||||
use crate::tools::*;
|
||||
use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct LogDecisionHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for LogDecisionHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"log_decision"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<LogDecisionTool>("log_decision", "Execute log_decision")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: LogDecisionTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let idx = state
|
||||
.search_index
|
||||
.read()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.clone();
|
||||
let mut final_id = String::new();
|
||||
|
||||
state.adrs.modify(|adrs| {
|
||||
final_id = format!("ADR-{:04}", adrs.len() + 1);
|
||||
let a = Adr {
|
||||
id: final_id.clone(),
|
||||
title: req.title,
|
||||
context: req.context,
|
||||
decision: req.decision,
|
||||
consequence: req.consequence,
|
||||
timestamp: crate::handlers::utils::now_secs(),
|
||||
};
|
||||
|
||||
drop(idx.index_adr(&a));
|
||||
adrs.push(a);
|
||||
});
|
||||
|
||||
Ok(format!("Decision logged as {}", final_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueryDecisionsHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for QueryDecisionsHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"query_decisions"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<QueryDecisionsTool>("query_decisions", "Execute query_decisions")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: QueryDecisionsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.adrs.read_with(|adrs| {
|
||||
let filtered: Vec<_> = adrs
|
||||
.iter()
|
||||
.filter(|a| {
|
||||
if let Some(q) = &req.query {
|
||||
contains_ignore_ascii_case(&a.title, q)
|
||||
|| contains_ignore_ascii_case(&a.context, q)
|
||||
|| contains_ignore_ascii_case(&a.decision, q)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogErrorFixHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for LogErrorFixHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"log_error_fix"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<LogErrorFixTool>("log_error_fix", "Execute log_error_fix")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: LogErrorFixTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.error_fixes.modify(|fixes| {
|
||||
fixes.push(crate::models::ErrorFix {
|
||||
signature: req.signature,
|
||||
solution: req.solution,
|
||||
timestamp: crate::handlers::utils::now_secs(),
|
||||
git_commit: req.git_commit,
|
||||
git_branch: req.git_branch,
|
||||
})
|
||||
});
|
||||
Ok("Error fix logged".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SearchErrorFixesHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for SearchErrorFixesHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"search_error_fixes"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<SearchErrorFixesTool>(
|
||||
"search_error_fixes",
|
||||
"Execute search_error_fixes",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: SearchErrorFixesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let q = req.query;
|
||||
let data = state.error_fixes.read_with(|fixes| {
|
||||
let filtered: Vec<_> = fixes
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
contains_ignore_ascii_case(&f.signature, &q)
|
||||
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogCodeChangeHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for LogCodeChangeHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"log_code_change"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<LogCodeChangeTool>("log_code_change", "Execute log_code_change")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: LogCodeChangeTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.ledger.modify(|ledger| {
|
||||
ledger.push(CodeChange {
|
||||
timestamp: crate::handlers::utils::now_secs(),
|
||||
file_path: req.file_path,
|
||||
description: req.description,
|
||||
git_commit: req.git_commit,
|
||||
git_branch: req.git_branch,
|
||||
});
|
||||
});
|
||||
Ok("Code change logged".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueryRecentChangesHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for QueryRecentChangesHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"query_recent_changes"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<QueryRecentChangesTool>(
|
||||
"query_recent_changes",
|
||||
"Execute query_recent_changes",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let data = state
|
||||
.ledger
|
||||
.read_with(|l| serde_json::to_string(l).unwrap_or_else(|_| "[]".to_string()));
|
||||
Ok(data.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LearnPreferenceHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for LearnPreferenceHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"learn_preference"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<LearnPreferenceTool>("learn_preference", "Execute learn_preference")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: LearnPreferenceTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.prefs.modify(|prefs| {
|
||||
prefs.insert(
|
||||
req.key.clone(),
|
||||
crate::models::Preference {
|
||||
key: req.key.clone(),
|
||||
value: req.value,
|
||||
updated_at: crate::handlers::utils::now_secs(),
|
||||
},
|
||||
);
|
||||
});
|
||||
Ok("Preference learned".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadPreferencesHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for ReadPreferencesHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"read_preferences"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<ReadPreferencesTool>("read_preferences", "Execute read_preferences")
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let data = state
|
||||
.prefs
|
||||
.read_with(|prefs| serde_json::to_string(prefs).unwrap_or_default());
|
||||
Ok(data.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogTechDebtHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for LogTechDebtHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"log_tech_debt"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<LogTechDebtTool>("log_tech_debt", "Execute log_tech_debt")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: LogTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
state.tech_debts.modify(|debts| {
|
||||
debts.push(crate::models::TechDebt {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
namespace: req.namespace,
|
||||
description: req.description,
|
||||
ideal_solution: req.ideal_solution,
|
||||
is_resolved: false,
|
||||
created_at: crate::handlers::utils::now_secs(),
|
||||
git_commit: req.git_commit,
|
||||
git_branch: req.git_branch,
|
||||
})
|
||||
});
|
||||
Ok("Tech debt logged".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResolveTechDebtHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for ResolveTechDebtHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"resolve_tech_debt"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<ResolveTechDebtTool>(
|
||||
"resolve_tech_debt",
|
||||
"Execute resolve_tech_debt",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: ResolveTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut found = false;
|
||||
state.tech_debts.modify(|debts| {
|
||||
for d in debts.iter_mut() {
|
||||
if d.id == req.id {
|
||||
d.is_resolved = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if found {
|
||||
Ok("Tech debt resolved".to_string())
|
||||
} else {
|
||||
Ok("Tech debt not found".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ListTechDebtHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for ListTechDebtHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"list_tech_debt"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<ListTechDebtTool>("list_tech_debt", "Execute list_tech_debt")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: ListTechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.tech_debts.read_with(|debts| {
|
||||
let filtered: Vec<_> = debts
|
||||
.iter()
|
||||
.filter(|d| {
|
||||
d.namespace == req.namespace && (req.include_resolved || !d.is_resolved)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OmniSearchHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for OmniSearchHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"omni_search"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<OmniSearchTool>("omni_search", "Execute omni_search")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: OmniSearchTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let matches = {
|
||||
let idx = state.search_index.read().unwrap_or_else(|e| e.into_inner());
|
||||
idx.search(&req.query, req.namespace.as_deref())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
let kg_json = state.read_graph(|full| {
|
||||
let mut kg_entities = std::collections::HashMap::new();
|
||||
for (id, doc_type, _, _, _) in &matches {
|
||||
if doc_type == "entity"
|
||||
&& let Some(e) = full.entities.get(id)
|
||||
{
|
||||
kg_entities.insert(id, e);
|
||||
}
|
||||
}
|
||||
serde_json::to_value(&kg_entities).unwrap_or_default()
|
||||
});
|
||||
|
||||
let tasks_json = state.tasks.read_with(|all_tasks| {
|
||||
let filtered: Vec<_> = all_tasks
|
||||
.iter()
|
||||
.filter(|t| {
|
||||
matches
|
||||
.iter()
|
||||
.any(|(id, typ, _, _, _)| id == &t.id && typ == "task")
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
|
||||
let snippets_json = state.snippets.read_with(|all_snippets| {
|
||||
let filtered: Vec<_> = all_snippets
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
matches
|
||||
.iter()
|
||||
.any(|(id, typ, _, _, _)| id == &s.name && typ == "snippet")
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
|
||||
let adrs_json = state.adrs.read_with(|all_adrs| {
|
||||
let filtered: Vec<_> = all_adrs
|
||||
.iter()
|
||||
.filter(|a| {
|
||||
matches
|
||||
.iter()
|
||||
.any(|(id, typ, _, _, _)| id == &a.id && typ == "adr")
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
|
||||
let q = req.query;
|
||||
let tech_debts_json = state.tech_debts.read_with(|debts| {
|
||||
let filtered: Vec<_> = debts
|
||||
.iter()
|
||||
.filter(|d| {
|
||||
req.namespace.as_ref().is_none_or(|ns| d.namespace == *ns)
|
||||
&& (contains_ignore_ascii_case(&d.description, &q)
|
||||
|| contains_ignore_ascii_case(&d.ideal_solution, &q))
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
|
||||
let memos_json = state.handoff_memos.read_with(|memos| {
|
||||
let filtered: Vec<_> = memos
|
||||
.iter()
|
||||
.filter(|m| {
|
||||
req.namespace.as_ref().is_none_or(|ns| m.namespace == *ns)
|
||||
&& contains_ignore_ascii_case(&m.content, &q)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
|
||||
let error_fixes_json = state.error_fixes.read_with(|fixes| {
|
||||
let filtered: Vec<_> = fixes
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
contains_ignore_ascii_case(&f.signature, &q)
|
||||
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
|
||||
let report = serde_json::json!({
|
||||
"knowledge_graph": kg_json,
|
||||
"tasks": tasks_json,
|
||||
"snippets": snippets_json,
|
||||
"adrs": adrs_json,
|
||||
"tech_debts": tech_debts_json,
|
||||
"handoff_memos": memos_json,
|
||||
"error_fixes": error_fixes_json
|
||||
});
|
||||
Ok(report.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GetProjectHealthHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for GetProjectHealthHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"get_project_health"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<GetProjectHealthTool>(
|
||||
"get_project_health",
|
||||
"Execute get_project_health",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let req: GetProjectHealthTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let active_tasks = state
|
||||
.tasks
|
||||
.read_with(|tasks| tasks.iter().filter(|t| t.status != "done").count());
|
||||
let unresolved_debt = state.tech_debts.read_with(|debts| {
|
||||
debts
|
||||
.iter()
|
||||
.filter(|d| d.namespace == req.namespace && !d.is_resolved)
|
||||
.count()
|
||||
});
|
||||
let unread_memos = state.handoff_memos.read_with(|memos| {
|
||||
memos
|
||||
.iter()
|
||||
.filter(|m| m.namespace == req.namespace)
|
||||
.count()
|
||||
});
|
||||
let active_milestones = state.milestones.read_with(|milestones| {
|
||||
milestones
|
||||
.iter()
|
||||
.filter(|m| m.namespace == req.namespace && m.status != "done")
|
||||
.count()
|
||||
});
|
||||
let remaining_checklists = state.pr_checklists.read_with(|checklists| {
|
||||
checklists
|
||||
.iter()
|
||||
.filter(|c| c.namespace == req.namespace)
|
||||
.count()
|
||||
});
|
||||
|
||||
let report = serde_json::json!({
|
||||
"active_tasks": active_tasks,
|
||||
"unresolved_tech_debt": unresolved_debt,
|
||||
"unread_handoff_memos": unread_memos,
|
||||
"active_milestones": active_milestones,
|
||||
"remaining_pr_checklist_items": remaining_checklists
|
||||
});
|
||||
Ok(report.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
use crate::handlers::utils::*;
|
||||
Reference in new issue
Block a user