chore(server): remove dead scratch scripts, temporary files, and unused legacy modules
This commit is contained in:
1 parent
1069096a29
commit
ce88bef45e
32 files changed
-1983
No files matched your search
@@ -1,62 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
def bake_nvim_safeguards():
|
||||
filepath = 'nvim-core/src/lib.rs'
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 1. We're going to inject an AST-level safeguard inside "nvim_execute_lua"
|
||||
# Find the execute_lua block
|
||||
target = """ "nvim_execute_lua" => {
|
||||
if let Some(code) = args.get("code").and_then(|v| v.as_str()) {
|
||||
match execute_nvim_lua(code).await {"""
|
||||
|
||||
safeguarded = """ "nvim_execute_lua" => {
|
||||
if let Some(code) = args.get("code").and_then(|v| v.as_str()) {
|
||||
// BAKE IN: Block interactive prompts that cause server deadlocks
|
||||
let lower_code = code.to_lowercase();
|
||||
if lower_code.contains("vim.fn.input") || lower_code.contains("vim.ui.select") || lower_code.contains("vim.fn.confirm") || lower_code.contains("vim.ui.input") {
|
||||
send_error(id, -32600, "CRITICAL ERROR: Interactive Lua functions (input, select, confirm) are strictly forbidden via MCP as they will hang the headless server.").await;
|
||||
return;
|
||||
}
|
||||
// Block the 'c' confirm flag in vim.cmd substitutions
|
||||
if (code.contains("vim.cmd") || code.contains("vim.api.nvim_command")) && code.contains("%s") && code.contains("c") {
|
||||
// A rough heuristic to block `%s/foo/bar/gc`
|
||||
if regex::Regex::new(r"%s.*/.*c").unwrap().is_match(code) {
|
||||
send_error(id, -32600, "CRITICAL ERROR: The 'c' (confirm) flag in Neovim substitutions is strictly forbidden via MCP as it triggers an interactive prompt that hangs the headless server. Use '/ge' instead.").await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match execute_nvim_lua(code).await {"""
|
||||
|
||||
if target in content:
|
||||
# Also need to add regex as a dependency or just use basic string matching.
|
||||
# Let's just use basic string matching for the %s block to avoid adding the regex crate to nvim-core if not present
|
||||
|
||||
safeguarded_no_regex = """ "nvim_execute_lua" => {
|
||||
if let Some(code) = args.get("code").and_then(|v| v.as_str()) {
|
||||
// BAKE IN: Block interactive prompts that cause server deadlocks
|
||||
let lower_code = code.to_lowercase();
|
||||
if lower_code.contains("vim.fn.input") || lower_code.contains("vim.ui.select") || lower_code.contains("vim.fn.confirm") || lower_code.contains("vim.ui.input") {
|
||||
send_error(id, -32600, "CRITICAL ERROR: Interactive Lua functions (input, select, confirm) are strictly forbidden via MCP as they will hang the headless server.").await;
|
||||
return;
|
||||
}
|
||||
// Block the 'c' confirm flag in vim.cmd substitutions
|
||||
if (code.contains("vim.cmd") || code.contains("vim.api.nvim_command")) && code.contains("%s") && (code.contains("gc'") || code.contains('gc"') || code.contains("gc\\n") || code.contains("c'") || code.contains('c"')) {
|
||||
send_error(id, -32600, "CRITICAL ERROR: The 'c' (confirm) flag in Neovim substitutions is strictly forbidden via MCP as it triggers an interactive prompt that hangs the headless server. Use '/g' or '/ge' instead.").await;
|
||||
return;
|
||||
}
|
||||
|
||||
match execute_nvim_lua(code).await {"""
|
||||
|
||||
content = content.replace(target, safeguarded_no_regex)
|
||||
print("Baked safeguards into nvim_execute_lua")
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
else:
|
||||
print("Could not find nvim_execute_lua block")
|
||||
|
||||
bake_nvim_safeguards()
|
||||
@@ -1,150 +0,0 @@
|
||||
import os
|
||||
|
||||
def rewrite_graph():
|
||||
with open('server/src/handlers_v2/graph.rs', 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
old_block = """ let mut included: HashSet<&str> = HashSet::new();
|
||||
let mut to_draw = Vec::new();
|
||||
|
||||
state.read_graph(|full| {
|
||||
for (name, e) in &full.entities {
|
||||
if let Some(ns) = &req.namespace
|
||||
&& e.namespace != *ns
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if query.is_empty()
|
||||
|| contains_ignore_ascii_case(name, &query)
|
||||
|| contains_ignore_ascii_case(&e.entity_type, &query)
|
||||
{
|
||||
included.insert(name.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
for r in &full.relations {
|
||||
if let Some(ns) = &req.namespace
|
||||
&& r.namespace != *ns
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if query.is_empty() || included.contains(r.from.as_str()) || included.contains(r.to.as_str()) {
|
||||
included.insert(r.from.as_str());
|
||||
included.insert(r.to.as_str());
|
||||
to_draw.push(r.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
use std::fmt::Write;
|
||||
let mut output = String::with_capacity(included.len() * 40 + to_draw.len() * 60);
|
||||
output.push_str("graph TD;\\n");
|
||||
|
||||
let sanitize = |s: &str, id_mode: bool| -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
if c != '"' && c != '(' && c != ')' {
|
||||
if id_mode && (c == ' ' || c == '-' || c == '.') {
|
||||
out.push('_');
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
};
|
||||
|
||||
for name in &included {
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" id_{}[\\"{}\\"];",
|
||||
sanitize(name, true),
|
||||
sanitize(name, false)
|
||||
);
|
||||
}
|
||||
for r in to_draw {
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" id_{}-->|\\"{}\\"|id_{};",
|
||||
sanitize(&r.from, true),
|
||||
r.relation_type.replace("\\"", ""),
|
||||
sanitize(&r.to, true)
|
||||
);
|
||||
}"""
|
||||
|
||||
new_block = """ use std::fmt::Write;
|
||||
let mut output = state.read_graph(|full| {
|
||||
let mut included: HashSet<&str> = HashSet::new();
|
||||
let mut to_draw = Vec::new();
|
||||
|
||||
for (name, e) in &full.entities {
|
||||
if let Some(ns) = &req.namespace
|
||||
&& e.namespace != *ns
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if query.is_empty()
|
||||
|| contains_ignore_ascii_case(name, &query)
|
||||
|| contains_ignore_ascii_case(&e.entity_type, &query)
|
||||
{
|
||||
included.insert(name.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
for r in &full.relations {
|
||||
if let Some(ns) = &req.namespace
|
||||
&& r.namespace != *ns
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if query.is_empty() || included.contains(r.from.as_str()) || included.contains(r.to.as_str()) {
|
||||
included.insert(r.from.as_str());
|
||||
included.insert(r.to.as_str());
|
||||
to_draw.push(r.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = String::with_capacity(included.len() * 40 + to_draw.len() * 60);
|
||||
out.push_str("graph TD;\\n");
|
||||
|
||||
let sanitize = |s: &str, id_mode: bool| -> String {
|
||||
let mut o = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
if c != '"' && c != '(' && c != ')' {
|
||||
if id_mode && (c == ' ' || c == '-' || c == '.') {
|
||||
o.push('_');
|
||||
} else {
|
||||
o.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
o
|
||||
};
|
||||
|
||||
for name in &included {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" id_{}[\\"{}\\"];",
|
||||
sanitize(name, true),
|
||||
sanitize(name, false)
|
||||
);
|
||||
}
|
||||
for r in to_draw {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" id_{}-->|\\"{}\\"|id_{};",
|
||||
sanitize(&r.from, true),
|
||||
r.relation_type.replace("\\"", ""),
|
||||
sanitize(&r.to, true)
|
||||
);
|
||||
}
|
||||
out
|
||||
});"""
|
||||
|
||||
if old_block in content:
|
||||
with open('server/src/handlers_v2/graph.rs', 'w', encoding='utf-8') as f:
|
||||
f.write(content.replace(old_block, new_block))
|
||||
print("Replaced visualize_graph")
|
||||
else:
|
||||
print("Could not find old block")
|
||||
|
||||
rewrite_graph()
|
||||
@@ -1,10 +0,0 @@
|
||||
use rmcp::model::{InitializeResult, ServerCapabilities};
|
||||
|
||||
fn main() {
|
||||
let init = InitializeResult::new(ServerCapabilities::builder().enable_tools().build())
|
||||
.with_server_info(rmcp::model::Implementation::new(
|
||||
"gemini-mcp-memory",
|
||||
"3.0.0",
|
||||
));
|
||||
println!("{}", serde_json::to_string_pretty(&init).unwrap());
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import os
|
||||
|
||||
def fix_env_and_workspaces():
|
||||
filepath = 'server/src/handlers_v2/env.rs'
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
old_env = """ let fps = state.env_fingerprints.read();
|
||||
if let Some(fp) = fps.get(&req.namespace) {"""
|
||||
|
||||
new_env = """ let data = state.env_fingerprints.read_with(|fps| {
|
||||
fps.get(&req.namespace).cloned()
|
||||
});
|
||||
if let Some(fp) = data {"""
|
||||
|
||||
if old_env in content:
|
||||
content = content.replace(old_env, new_env)
|
||||
print('Replaced env read')
|
||||
else:
|
||||
print('Could not find old_env')
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
filepath = 'server/src/handlers_v2/workspaces.rs'
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
old_ws = """ let query = req.query.to_lowercase();
|
||||
let snippets = state.snippets.read();
|
||||
let mut results = Vec::new();
|
||||
for s in snippets {
|
||||
if contains_ignore_ascii_case(&s.name, &query)
|
||||
|| contains_ignore_ascii_case(&s.description, &query)
|
||||
|| contains_ignore_ascii_case(&s.language, &query)
|
||||
{
|
||||
results.push(s);
|
||||
}
|
||||
}
|
||||
let data = serde_json::to_string(&results).unwrap_or_default();"""
|
||||
|
||||
new_ws = """ let query = req.query.to_lowercase();
|
||||
let data = state.snippets.read_with(|snippets| {
|
||||
let results: Vec<_> = snippets.iter().filter(|s| {
|
||||
contains_ignore_ascii_case(&s.name, &query)
|
||||
|| contains_ignore_ascii_case(&s.description, &query)
|
||||
|| contains_ignore_ascii_case(&s.language, &query)
|
||||
}).cloned().collect();
|
||||
serde_json::to_string(&results).unwrap_or_default()
|
||||
});"""
|
||||
|
||||
if old_ws in content:
|
||||
content = content.replace(old_ws, new_ws)
|
||||
print('Replaced workspaces read')
|
||||
else:
|
||||
print('Could not find old_ws')
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
fix_env_and_workspaces()
|
||||
@@ -1,60 +0,0 @@
|
||||
import os
|
||||
|
||||
def fix_main_stats():
|
||||
filepath = 'server/src/main.rs'
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
old_stats = """ let (entities, relations) = {
|
||||
let graph = state_clone.get_full_graph();
|
||||
(graph.entities.len(), graph.relations.len())
|
||||
};
|
||||
let tasks = state_clone.tasks.read().len();
|
||||
let snippets = state_clone.snippets.read().len();
|
||||
let tech_debts = state_clone.tech_debts.read().len();
|
||||
let adrs = state_clone.adrs.read().len();
|
||||
|
||||
let ledger = state_clone.ledger.read().len();
|
||||
let sticky = state_clone.sticky.read().len();
|
||||
let error_fixes = state_clone.error_fixes.read().len();
|
||||
let pinned_files = state_clone.pinned_files.read().len();
|
||||
let session_summaries = state_clone.session_summaries.read().len();
|
||||
let handoff_memos = state_clone.handoff_memos.read().len();
|
||||
let env_fingerprints = state_clone.env_fingerprints.read().len();
|
||||
let env_requirements = state_clone.env_requirements.read().len();
|
||||
let milestones = state_clone.milestones.read().len();
|
||||
let environments = state_clone.environments.read().len();
|
||||
let pr_checklists = state_clone.pr_checklists.read().len();
|
||||
let gates = state_clone.gates.read().len();
|
||||
let context_workspaces = state_clone.context_workspaces.read().len();"""
|
||||
|
||||
new_stats = """ let (entities, relations) = state_clone.read_graph(|g| (g.entities.len(), g.relations.len()));
|
||||
let tasks = state_clone.tasks.read_with(|items| items.len());
|
||||
let snippets = state_clone.snippets.read_with(|items| items.len());
|
||||
let tech_debts = state_clone.tech_debts.read_with(|items| items.len());
|
||||
let adrs = state_clone.adrs.read_with(|items| items.len());
|
||||
|
||||
let ledger = state_clone.ledger.read_with(|items| items.len());
|
||||
let sticky = state_clone.sticky.read_with(|items| items.len());
|
||||
let error_fixes = state_clone.error_fixes.read_with(|items| items.len());
|
||||
let pinned_files = state_clone.pinned_files.read_with(|items| items.len());
|
||||
let session_summaries = state_clone.session_summaries.read_with(|items| items.len());
|
||||
let handoff_memos = state_clone.handoff_memos.read_with(|items| items.len());
|
||||
let env_fingerprints = state_clone.env_fingerprints.read_with(|items| items.len());
|
||||
let env_requirements = state_clone.env_requirements.read_with(|items| items.len());
|
||||
let milestones = state_clone.milestones.read_with(|items| items.len());
|
||||
let environments = state_clone.environments.read_with(|items| items.len());
|
||||
let pr_checklists = state_clone.pr_checklists.read_with(|items| items.len());
|
||||
let gates = state_clone.gates.read_with(|items| items.len());
|
||||
let context_workspaces = state_clone.context_workspaces.read_with(|items| items.len());"""
|
||||
|
||||
if old_stats in content:
|
||||
content = content.replace(old_stats, new_stats)
|
||||
print('Replaced stats reads')
|
||||
else:
|
||||
print('Could not find old_stats')
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
fix_main_stats()
|
||||
@@ -1,111 +0,0 @@
|
||||
import os
|
||||
|
||||
def fix_meta():
|
||||
filepath = 'server/src/handlers_v2/meta.rs'
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
old_omni = """ let q = req.query.to_lowercase();
|
||||
let tech_debts: Vec<_> = state
|
||||
.tech_debts
|
||||
.read()
|
||||
.into_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();
|
||||
let memos: Vec<_> = state
|
||||
.handoff_memos
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|m| {
|
||||
req.namespace.as_ref().is_none_or(|ns| m.namespace == *ns)
|
||||
&& contains_ignore_ascii_case(&m.content, &q)
|
||||
})
|
||||
.collect();
|
||||
let error_fixes: Vec<_> = state
|
||||
.error_fixes
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|f| {
|
||||
contains_ignore_ascii_case(&f.signature, &q)
|
||||
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||
})
|
||||
.collect();"""
|
||||
|
||||
new_omni = """ let q = req.query.to_lowercase();
|
||||
let tech_debts = state.tech_debts.read_with(|debts| {
|
||||
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))
|
||||
}).cloned().collect::<Vec<_>>()
|
||||
});
|
||||
let memos = state.handoff_memos.read_with(|memos| {
|
||||
memos.iter().filter(|m| {
|
||||
req.namespace.as_ref().is_none_or(|ns| m.namespace == *ns)
|
||||
&& contains_ignore_ascii_case(&m.content, &q)
|
||||
}).cloned().collect::<Vec<_>>()
|
||||
});
|
||||
let error_fixes = state.error_fixes.read_with(|fixes| {
|
||||
fixes.iter().filter(|f| {
|
||||
contains_ignore_ascii_case(&f.signature, &q)
|
||||
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||
}).cloned().collect::<Vec<_>>()
|
||||
});"""
|
||||
|
||||
if old_omni in content:
|
||||
content = content.replace(old_omni, new_omni)
|
||||
print('Replaced omni_search vecs')
|
||||
else:
|
||||
print('Could not find old_omni')
|
||||
|
||||
old_health = """ let active_tasks = state
|
||||
.tasks
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|t| t.status != "done")
|
||||
.count();
|
||||
let unresolved_debt = state
|
||||
.tech_debts
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|d| d.namespace == req.namespace && !d.is_resolved)
|
||||
.count();
|
||||
let unread_memos = state
|
||||
.handoff_memos
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|m| m.namespace == req.namespace)
|
||||
.count();
|
||||
let active_milestones = state
|
||||
.milestones
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|m| m.namespace == req.namespace && m.status != "done")
|
||||
.count();
|
||||
let remaining_checklists = state
|
||||
.pr_checklists
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|c| c.namespace == req.namespace)
|
||||
.count();"""
|
||||
|
||||
new_health = """ 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());"""
|
||||
|
||||
if old_health in content:
|
||||
content = content.replace(old_health, new_health)
|
||||
print('Replaced health counters')
|
||||
else:
|
||||
print('Could not find old_health')
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
fix_meta()
|
||||
@@ -1,44 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
def fix_nvim_msgid():
|
||||
filepath = 'nvim-core/src/lib.rs'
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Add atomic import and static var if not exists
|
||||
if 'static NEXT_MSGID' not in content:
|
||||
atomic_def = "use std::sync::atomic::{AtomicU64, Ordering};\nstatic NEXT_MSGID: AtomicU64 = AtomicU64::new(1);\n"
|
||||
# Find NVIM_CONN
|
||||
conn_idx = content.find('static NVIM_CONN')
|
||||
if conn_idx != -1:
|
||||
content = content[:conn_idx] + atomic_def + content[conn_idx:]
|
||||
|
||||
# Replace all hardcoded msgid
|
||||
# e.g., RmpValue::Integer(1.into()), // msgid
|
||||
# with: let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst); ... RmpValue::Integer(msgid.into()),
|
||||
|
||||
# We need to insert `let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);` before `let req = RmpValue::Array(vec![`
|
||||
|
||||
# We will use regex to find the blocks
|
||||
funcs = [
|
||||
('send_nvim_command', '1'),
|
||||
('get_nvim_active_buffer', '2'),
|
||||
('get_nvim_cursor', '3'),
|
||||
('get_nvim_visual_selection', '4'),
|
||||
('set_nvim_diagnostics', '5'),
|
||||
('execute_nvim_lua', '6'),
|
||||
]
|
||||
|
||||
for func, old_id in funcs:
|
||||
pattern = rf"let req = RmpValue::Array\(vec!\[\s*RmpValue::Integer\(0\.into\(\)\),\s*RmpValue::Integer\({old_id}\.into\(\)\), // msgid"
|
||||
replacement = f"let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);\n let req = RmpValue::Array(vec![\n RmpValue::Integer(0.into()),\n RmpValue::Integer(msgid.into()), // msgid"
|
||||
|
||||
content = re.sub(pattern, replacement, content)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print('Fixed msgid allocations')
|
||||
|
||||
fix_nvim_msgid()
|
||||
@@ -1,40 +0,0 @@
|
||||
import os
|
||||
|
||||
def fix_notes():
|
||||
filepath = 'server/src/handlers_v2/notes.rs'
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
old_standup = """ let tasks = state
|
||||
.tasks
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|t| t.updated_at >= cutoff)
|
||||
.collect::<Vec<_>>();
|
||||
let changes = state
|
||||
.ledger
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|c| c.timestamp >= cutoff)
|
||||
.collect::<Vec<_>>();
|
||||
let summaries = state
|
||||
.session_summaries
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|s| s.namespace == req.namespace && s.timestamp >= cutoff)
|
||||
.collect::<Vec<_>>();"""
|
||||
|
||||
new_standup = """ let tasks = state.tasks.read_with(|items| items.iter().filter(|t| t.updated_at >= cutoff).cloned().collect::<Vec<_>>());
|
||||
let changes = state.ledger.read_with(|items| items.iter().filter(|c| c.timestamp >= cutoff).cloned().collect::<Vec<_>>());
|
||||
let summaries = state.session_summaries.read_with(|items| items.iter().filter(|s| s.namespace == req.namespace && s.timestamp >= cutoff).cloned().collect::<Vec<_>>());"""
|
||||
|
||||
if old_standup in content:
|
||||
content = content.replace(old_standup, new_standup)
|
||||
print('Replaced standup in notes.rs')
|
||||
else:
|
||||
print('Could not find old_standup in notes.rs')
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
fix_notes()
|
||||
@@ -1,66 +0,0 @@
|
||||
import os
|
||||
|
||||
def fix_stub_leaks():
|
||||
filepath = 'stub/src/main.rs'
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 1. Fix connect_async to handle shutdown and timeout
|
||||
old_connect = """ match tokio_tungstenite::connect_async(request).await {"""
|
||||
new_connect = """ let connect_result = tokio::select! {
|
||||
_ = shutdown_rx.recv() => {
|
||||
tracing::info!("Shutdown received during connect");
|
||||
break;
|
||||
}
|
||||
res = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(5),
|
||||
tokio_tungstenite::connect_async(request)
|
||||
) => res,
|
||||
};
|
||||
|
||||
match connect_result {
|
||||
Ok(Ok((ws_stream, _))) => {"""
|
||||
|
||||
if old_connect in content:
|
||||
content = content.replace(old_connect, new_connect)
|
||||
print("Replaced connect_async")
|
||||
|
||||
# Fix Err block to match the new match structure
|
||||
old_err = """ Err(e) => {
|
||||
tracing::error!("Failed to connect via WSS: {}", e);
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
}"""
|
||||
new_err = """ Ok(Err(e)) => {
|
||||
tracing::error!("Failed to connect via WSS: {}", e);
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::error!("Connection attempt timed out");
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
}"""
|
||||
|
||||
if old_err in content:
|
||||
content = content.replace(old_err, new_err)
|
||||
print("Replaced Err branch")
|
||||
|
||||
# 2. Fix the break in send_task that exits the stub instead of reconnecting
|
||||
old_select_send = """ _ = &mut send_task => {
|
||||
tracing::error!("Send task exited");
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||
recv_task.abort();
|
||||
break;
|
||||
}"""
|
||||
new_select_send = """ _ = &mut send_task => {
|
||||
tracing::error!("Send task exited");
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||
recv_task.abort();
|
||||
}"""
|
||||
|
||||
if old_select_send in content:
|
||||
content = content.replace(old_select_send, new_select_send)
|
||||
print("Replaced select send_task")
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
fix_stub_leaks()
|
||||
@@ -1,197 +0,0 @@
|
||||
use crate::router::McpTool;
|
||||
use crate::state::MemoryState;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct MemoryHandler {
|
||||
pub state: Arc<MemoryState>,
|
||||
pub tools: std::collections::HashMap<String, Box<dyn McpTool>>,
|
||||
}
|
||||
|
||||
impl MemoryHandler {
|
||||
pub fn new(state: Arc<MemoryState>) -> Self {
|
||||
let mut tools: std::collections::HashMap<String, Box<dyn McpTool>> =
|
||||
std::collections::HashMap::new();
|
||||
|
||||
macro_rules! register {
|
||||
($module:ident::$handler:ident) => {
|
||||
let h = crate::handlers_v2::$module::$handler;
|
||||
tools.insert(h.name().to_string(), Box::new(h));
|
||||
};
|
||||
}
|
||||
|
||||
register!(graph::QueryGraphPathHandler);
|
||||
register!(graph::CreateEntitiesHandler);
|
||||
register!(graph::CreateRelationsHandler);
|
||||
register!(graph::AddObservationsHandler);
|
||||
register!(graph::DeleteEntitiesHandler);
|
||||
register!(graph::DeleteObservationsHandler);
|
||||
register!(graph::DeleteRelationsHandler);
|
||||
register!(graph::ReadGraphHandler);
|
||||
register!(graph::SearchNodesHandler);
|
||||
register!(graph::OpenNodesHandler);
|
||||
register!(graph::VisualizeGraphHandler);
|
||||
register!(graph::CondenseEntityHandler);
|
||||
register!(graph::MergeEntitiesHandler);
|
||||
register!(graph::FindOrphansHandler);
|
||||
|
||||
register!(tasks::AddTaskHandler);
|
||||
register!(tasks::DeleteTaskHandler);
|
||||
register!(tasks::UpdateTaskStatusHandler);
|
||||
register!(tasks::ListActiveTasksHandler);
|
||||
register!(tasks::SetAcceptanceCriteriaHandler);
|
||||
register!(tasks::VerifyAcceptanceCriteriaHandler);
|
||||
register!(tasks::AddMilestoneHandler);
|
||||
register!(tasks::UpdateMilestoneHandler);
|
||||
register!(tasks::ListMilestonesHandler);
|
||||
|
||||
register!(notes::AddStickyNoteHandler);
|
||||
register!(notes::ReadStickyNotesHandler);
|
||||
register!(notes::DeleteStickyNoteHandler);
|
||||
register!(notes::ClearStickyNotesHandler);
|
||||
register!(notes::LeaveHandoffMemoHandler);
|
||||
register!(notes::ReadHandoffMemosHandler);
|
||||
register!(notes::ClearHandoffMemosHandler);
|
||||
register!(notes::AddSessionSummaryHandler);
|
||||
register!(notes::GenerateStandupReportHandler);
|
||||
|
||||
register!(meta::LogDecisionHandler);
|
||||
register!(meta::QueryDecisionsHandler);
|
||||
register!(meta::LogErrorFixHandler);
|
||||
register!(meta::SearchErrorFixesHandler);
|
||||
register!(meta::LogCodeChangeHandler);
|
||||
register!(meta::QueryRecentChangesHandler);
|
||||
register!(meta::LearnPreferenceHandler);
|
||||
register!(meta::ReadPreferencesHandler);
|
||||
register!(meta::LogTechDebtHandler);
|
||||
register!(meta::ResolveTechDebtHandler);
|
||||
register!(meta::ListTechDebtHandler);
|
||||
register!(meta::OmniSearchHandler);
|
||||
register!(meta::GetProjectHealthHandler);
|
||||
|
||||
register!(env::UpdateEnvFingerprintHandler);
|
||||
register!(env::ReadEnvFingerprintHandler);
|
||||
register!(env::LogEnvRequirementHandler);
|
||||
register!(env::RegisterEnvironmentHandler);
|
||||
register!(env::GetEnvironmentDetailsHandler);
|
||||
|
||||
register!(workspaces::PinFileHandler);
|
||||
register!(workspaces::UnpinFileHandler);
|
||||
register!(workspaces::ListPinnedFilesHandler);
|
||||
register!(workspaces::StoreSnippetHandler);
|
||||
register!(workspaces::SearchSnippetsHandler);
|
||||
register!(workspaces::DeleteSnippetHandler);
|
||||
register!(workspaces::SaveContextWorkspaceHandler);
|
||||
register!(workspaces::LoadContextWorkspaceHandler);
|
||||
register!(workspaces::ListContextWorkspacesHandler);
|
||||
register!(workspaces::AddPrChecklistItemHandler);
|
||||
register!(workspaces::GetPrChecklistHandler);
|
||||
register!(workspaces::ClearPrChecklistHandler);
|
||||
|
||||
Self { state, tools }
|
||||
}
|
||||
|
||||
pub async fn handle_request(&self, req: serde_json::Value) -> Option<serde_json::Value> {
|
||||
let id = req.get("id").cloned().unwrap_or(serde_json::Value::Null);
|
||||
let id_clone = id.clone();
|
||||
let method = req.get("method").and_then(|m| m.as_str()).unwrap_or("");
|
||||
|
||||
match method {
|
||||
"server/discover" => {
|
||||
let payload = serde_json::json!({
|
||||
"resultType": "complete",
|
||||
"ttlMs": 0,
|
||||
"cacheScope": "public",
|
||||
"supportedVersions": ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"],
|
||||
"capabilities": {
|
||||
"tools": serde_json::json!({})
|
||||
},
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/serverInfo": {
|
||||
"name": "gemini-mcp-memory",
|
||||
"version": "3.0.0"
|
||||
}
|
||||
}
|
||||
});
|
||||
Some(crate::mcp::success(id, payload))
|
||||
}
|
||||
"initialize" => {
|
||||
let init = rmcp::model::InitializeResult::new(
|
||||
rmcp::model::ServerCapabilities::builder()
|
||||
.enable_tools()
|
||||
.build(),
|
||||
)
|
||||
.with_server_info(rmcp::model::Implementation::new(
|
||||
"gemini-mcp-memory",
|
||||
"3.0.0",
|
||||
));
|
||||
Some(crate::mcp::success(
|
||||
id,
|
||||
serde_json::to_value(&init).unwrap_or_default(),
|
||||
))
|
||||
}
|
||||
"notifications/initialized" => None,
|
||||
"tools/list" => {
|
||||
let mut tools: Vec<serde_json::Value> =
|
||||
self.tools.values().map(|t| t.schema()).collect();
|
||||
tools.sort_by_key(|t| {
|
||||
t.get("name")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
});
|
||||
Some(crate::mcp::success(
|
||||
id,
|
||||
serde_json::json!({ "tools": tools }),
|
||||
))
|
||||
}
|
||||
"tools/call" => {
|
||||
let params = req.get("params").unwrap_or(&serde_json::Value::Null);
|
||||
let name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
|
||||
let args = params
|
||||
.get("arguments")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Object(Default::default()));
|
||||
|
||||
self.state
|
||||
.broadcast_activity(&format!("Agent executed tool: {}", name));
|
||||
|
||||
let result: Result<String, String> = if let Some(tool) = self.tools.get(name) {
|
||||
tool.execute(args, self.state.clone()).await
|
||||
} else {
|
||||
Err(format!("Unknown tool: {}", name))
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(text) => {
|
||||
let payload = serde_json::json!({
|
||||
"content": [{"type": "text", "text": text}],
|
||||
"isError": false
|
||||
});
|
||||
Some(crate::mcp::success(id_clone, payload))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Tool {} failed: {}", name, e);
|
||||
let payload = serde_json::json!({
|
||||
"content": [{"type": "text", "text": e}],
|
||||
"isError": true
|
||||
});
|
||||
Some(crate::mcp::success(id_clone, payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
m if m.starts_with("notifications/") => None,
|
||||
"ping" => Some(crate::mcp::success(id, serde_json::json!({}))),
|
||||
_ => {
|
||||
if id.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(crate::mcp::error(
|
||||
id,
|
||||
-32601,
|
||||
&format!("Method {} not found", method),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
GROUPS = {
|
||||
"graph": [
|
||||
"query_graph_path", "create_entities", "create_relations", "add_observations",
|
||||
"delete_entities", "delete_observations", "delete_relations", "read_graph",
|
||||
"search_nodes", "open_nodes", "visualize_graph", "condense_entity",
|
||||
"merge_entities", "find_orphans"
|
||||
],
|
||||
"tasks": [
|
||||
"add_task", "delete_task", "update_task_status", "list_active_tasks",
|
||||
"set_acceptance_criteria", "verify_acceptance_criteria",
|
||||
"add_milestone", "update_milestone", "list_milestones"
|
||||
],
|
||||
"notes": [
|
||||
"add_sticky_note", "read_sticky_notes", "delete_sticky_note", "clear_sticky_notes",
|
||||
"leave_handoff_memo", "read_handoff_memos", "clear_handoff_memos",
|
||||
"add_session_summary", "generate_standup_report"
|
||||
],
|
||||
"meta": [
|
||||
"log_decision", "query_decisions", "log_error_fix", "search_error_fixes",
|
||||
"log_code_change", "query_recent_changes", "learn_preference", "read_preferences",
|
||||
"log_tech_debt", "resolve_tech_debt", "list_tech_debt", "omni_search", "get_project_health"
|
||||
],
|
||||
"env": [
|
||||
"update_env_fingerprint", "read_env_fingerprint", "log_env_requirement",
|
||||
"register_environment", "get_environment_details"
|
||||
],
|
||||
"workspaces": [
|
||||
"pin_file", "unpin_file", "list_pinned_files", "store_snippet", "search_snippets",
|
||||
"delete_snippet", "save_context_workspace", "load_context_workspace",
|
||||
"list_context_workspaces", "add_pr_checklist_item", "get_pr_checklist", "clear_pr_checklist"
|
||||
]
|
||||
}
|
||||
|
||||
def to_camel_case(snake_str):
|
||||
components = snake_str.split('_')
|
||||
return "".join(x.title() for x in components)
|
||||
|
||||
def parse_rust_match(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
start_idx = -1
|
||||
for i, line in enumerate(lines):
|
||||
if "let result: Result<String, String> = match name {" in line:
|
||||
start_idx = i
|
||||
break
|
||||
|
||||
if start_idx == -1:
|
||||
return {}
|
||||
|
||||
brace_depth = 1
|
||||
i = start_idx + 1
|
||||
|
||||
tools = {}
|
||||
current_tool = None
|
||||
current_body = []
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
if brace_depth == 1 and "=>" in line and '"' in line:
|
||||
parts = line.strip().split('"')
|
||||
if len(parts) >= 3:
|
||||
tool_name = parts[1]
|
||||
current_tool = tool_name
|
||||
current_body = []
|
||||
# Don't add the "name" => { line
|
||||
|
||||
if current_tool is not None and not (brace_depth == 1 and "=>" in line and '"' in line):
|
||||
# check if this line closes the block
|
||||
next_depth = brace_depth + line.count('{') - line.count('}')
|
||||
if next_depth == 1 and current_tool is not None:
|
||||
# This is the closing brace
|
||||
tools[current_tool] = "".join(current_body)
|
||||
current_tool = None
|
||||
else:
|
||||
current_body.append(line)
|
||||
|
||||
brace_depth += line.count('{')
|
||||
brace_depth -= line.count('}')
|
||||
|
||||
if brace_depth == 0:
|
||||
break
|
||||
|
||||
i += 1
|
||||
|
||||
return tools
|
||||
|
||||
def transform_body(body):
|
||||
# Transform parse_tool!
|
||||
body = re.sub(
|
||||
r'let req = parse_tool!\(args, id, ([^)]+)\);',
|
||||
r'let req: \1 = serde_json::from_value(args).map_err(|e| e.to_string())?;',
|
||||
body
|
||||
)
|
||||
# Transform handle_list_with_namespace!
|
||||
def repl_handle_list(m):
|
||||
store = m.group(1)
|
||||
tool_type = m.group(2)
|
||||
return f"""
|
||||
let req: {tool_type} = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut items = state.{store}.read();
|
||||
if let Some(ns) = req.namespace {{
|
||||
items.retain(|i| i.namespace == ns);
|
||||
}}
|
||||
let data = serde_json::to_string(&items).unwrap_or_default();
|
||||
return Ok(data.to_string());
|
||||
"""
|
||||
body = re.sub(
|
||||
r'return handle_list_with_namespace!\(self, ([^,]+), ([^,]+), args, id\);',
|
||||
repl_handle_list,
|
||||
body
|
||||
)
|
||||
|
||||
# Replace self.state with state
|
||||
body = body.replace("self.state.", "state.")
|
||||
|
||||
return body
|
||||
|
||||
|
||||
tools = parse_rust_match("server/src/handlers.rs")
|
||||
|
||||
for group, tool_names in GROUPS.items():
|
||||
file_path = f"server/src/handlers_v2/{group}.rs"
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write("use crate::router::McpTool;\n")
|
||||
f.write("use crate::state::MemoryState;\n")
|
||||
f.write("use crate::tools::*;\n")
|
||||
f.write("use async_trait::async_trait;\n")
|
||||
f.write("use serde_json::Value;\n")
|
||||
f.write("use std::sync::Arc;\n")
|
||||
f.write("use std::time::{SystemTime, UNIX_EPOCH};\n\n")
|
||||
|
||||
for name in tool_names:
|
||||
if name not in tools:
|
||||
continue
|
||||
|
||||
body = tools[name]
|
||||
# special case for query_graph_path which we already wrote properly?
|
||||
# actually we will just overwrite it with the transformed body
|
||||
body = transform_body(body)
|
||||
|
||||
struct_name = to_camel_case(name) + "Handler"
|
||||
tool_type = to_camel_case(name) + "Tool"
|
||||
|
||||
f.write(f"pub struct {struct_name};\n\n")
|
||||
f.write(f"#[async_trait]\n")
|
||||
f.write(f"impl McpTool for {struct_name} {{\n")
|
||||
f.write(f" fn name(&self) -> &'static str {{\n")
|
||||
f.write(f' "{name}"\n')
|
||||
f.write(f" }}\n\n")
|
||||
f.write(f" fn schema(&self) -> Value {{\n")
|
||||
# For schema description we can just put a generic one or extract it.
|
||||
# I will use a generic one for now, or you can extract it from tools/list.
|
||||
f.write(f' crate::mcp::tool_def::<{tool_type}>(\n')
|
||||
f.write(f' "{name}",\n')
|
||||
f.write(f' "Execute {name}",\n')
|
||||
f.write(f' )\n')
|
||||
f.write(f" }}\n\n")
|
||||
f.write(f" async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {{\n")
|
||||
f.write(body)
|
||||
f.write(f" }}\n")
|
||||
f.write(f"}}\n\n")
|
||||
|
||||
print("Generated handlers_v2 modules")
|
||||
|
||||
# generate mod.rs
|
||||
with open("server/src/handlers_v2/mod.rs", "w", encoding="utf-8") as f:
|
||||
for group in GROUPS.keys():
|
||||
f.write(f"pub mod {group};\n")
|
||||
@@ -1,11 +0,0 @@
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
|
||||
fn main() {
|
||||
let content = fs::read_to_string("server/src/handlers.rs").unwrap();
|
||||
println!("Read {} bytes", content.len());
|
||||
// Find the match name { block
|
||||
let match_start = content.find("match name {").unwrap();
|
||||
// naive extraction
|
||||
println!("Found match block at {}", match_start);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
with open("server/src/handlers.rs", "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
list_start = -1
|
||||
for i, line in enumerate(lines):
|
||||
if '"tools/list" => {' in line:
|
||||
list_start = i
|
||||
break
|
||||
|
||||
# Find end of tools/call
|
||||
call_start = -1
|
||||
for i in range(list_start, len(lines)):
|
||||
if '"tools/call" => {' in line:
|
||||
call_start = i
|
||||
break
|
||||
|
||||
# Find end of tools/call
|
||||
# Match brace depth from call_start
|
||||
brace_depth = 1
|
||||
call_end = -1
|
||||
for i in range(call_start + 1, len(lines)):
|
||||
brace_depth += lines[i].count('{')
|
||||
brace_depth -= lines[i].count('}')
|
||||
if brace_depth == 0:
|
||||
call_end = i
|
||||
break
|
||||
|
||||
# replacement block
|
||||
replacement = """ "tools/list" => {
|
||||
let mut tools: Vec<serde_json::Value> = self.tools.values().map(|t| t.schema()).collect();
|
||||
tools.sort_by_key(|t| t.get("name").and_then(|n| n.as_str()).unwrap_or("").to_string());
|
||||
Some(crate::mcp::success(
|
||||
id,
|
||||
serde_json::json!({ "tools": tools }),
|
||||
))
|
||||
}
|
||||
"tools/call" => {
|
||||
let params = req.get("params").unwrap_or(&serde_json::Value::Null);
|
||||
let name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
|
||||
let args = params
|
||||
.get("arguments")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Object(Default::default()));
|
||||
|
||||
self.state
|
||||
.broadcast_activity(&format!("Agent executed tool: {}", name));
|
||||
|
||||
let result: Result<String, String> = if let Some(tool) = self.tools.get(name) {
|
||||
tool.execute(args, self.state.clone()).await
|
||||
} else {
|
||||
Err(format!("Unknown tool: {}", name))
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(text) => {
|
||||
let payload = serde_json::json!({
|
||||
"content": [{"type": "text", "text": text}],
|
||||
"isError": false
|
||||
});
|
||||
Some(crate::mcp::success(id_clone, payload))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Tool {} failed: {}", name, e);
|
||||
let payload = serde_json::json!({
|
||||
"content": [{"type": "text", "text": e}],
|
||||
"isError": true
|
||||
});
|
||||
Some(crate::mcp::success(id_clone, payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
new_lines = lines[:list_start] + [replacement] + lines[call_end+1:]
|
||||
|
||||
with open("server/src/handlers.rs", "w", encoding="utf-8") as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
print("tools/list and tools/call replaced.")
|
||||
@@ -1,106 +0,0 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
with open("server/src/handlers.rs", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace MemoryHandler struct
|
||||
struct_pattern = r'pub struct MemoryHandler \{\s*pub state: Arc<MemoryState>,\s*\}'
|
||||
|
||||
new_struct = """use crate::router::McpTool;
|
||||
|
||||
pub struct MemoryHandler {
|
||||
pub state: Arc<MemoryState>,
|
||||
pub tools: std::collections::HashMap<String, Box<dyn McpTool>>,
|
||||
}
|
||||
|
||||
impl MemoryHandler {
|
||||
pub fn new(state: Arc<MemoryState>) -> Self {
|
||||
let mut tools: std::collections::HashMap<String, Box<dyn McpTool>> = std::collections::HashMap::new();
|
||||
|
||||
macro_rules! register {
|
||||
($module:ident::$handler:ident) => {
|
||||
let h = crate::handlers_v2::$module::$handler;
|
||||
tools.insert(h.name().to_string(), Box::new(h));
|
||||
};
|
||||
}
|
||||
|
||||
register!(graph::QueryGraphPathHandler);
|
||||
register!(graph::CreateEntitiesHandler);
|
||||
register!(graph::CreateRelationsHandler);
|
||||
register!(graph::AddObservationsHandler);
|
||||
register!(graph::DeleteEntitiesHandler);
|
||||
register!(graph::DeleteObservationsHandler);
|
||||
register!(graph::DeleteRelationsHandler);
|
||||
register!(graph::ReadGraphHandler);
|
||||
register!(graph::SearchNodesHandler);
|
||||
register!(graph::OpenNodesHandler);
|
||||
register!(graph::VisualizeGraphHandler);
|
||||
register!(graph::CondenseEntityHandler);
|
||||
register!(graph::MergeEntitiesHandler);
|
||||
register!(graph::FindOrphansHandler);
|
||||
|
||||
register!(tasks::AddTaskHandler);
|
||||
register!(tasks::DeleteTaskHandler);
|
||||
register!(tasks::UpdateTaskStatusHandler);
|
||||
register!(tasks::ListActiveTasksHandler);
|
||||
register!(tasks::SetAcceptanceCriteriaHandler);
|
||||
register!(tasks::VerifyAcceptanceCriteriaHandler);
|
||||
register!(tasks::AddMilestoneHandler);
|
||||
register!(tasks::UpdateMilestoneHandler);
|
||||
register!(tasks::ListMilestonesHandler);
|
||||
|
||||
register!(notes::AddStickyNoteHandler);
|
||||
register!(notes::ReadStickyNotesHandler);
|
||||
register!(notes::DeleteStickyNoteHandler);
|
||||
register!(notes::ClearStickyNotesHandler);
|
||||
register!(notes::LeaveHandoffMemoHandler);
|
||||
register!(notes::ReadHandoffMemosHandler);
|
||||
register!(notes::ClearHandoffMemosHandler);
|
||||
register!(notes::AddSessionSummaryHandler);
|
||||
register!(notes::GenerateStandupReportHandler);
|
||||
|
||||
register!(meta::LogDecisionHandler);
|
||||
register!(meta::QueryDecisionsHandler);
|
||||
register!(meta::LogErrorFixHandler);
|
||||
register!(meta::SearchErrorFixesHandler);
|
||||
register!(meta::LogCodeChangeHandler);
|
||||
register!(meta::QueryRecentChangesHandler);
|
||||
register!(meta::LearnPreferenceHandler);
|
||||
register!(meta::ReadPreferencesHandler);
|
||||
register!(meta::LogTechDebtHandler);
|
||||
register!(meta::ResolveTechDebtHandler);
|
||||
register!(meta::ListTechDebtHandler);
|
||||
register!(meta::OmniSearchHandler);
|
||||
register!(meta::GetProjectHealthHandler);
|
||||
|
||||
register!(env::UpdateEnvFingerprintHandler);
|
||||
register!(env::ReadEnvFingerprintHandler);
|
||||
register!(env::LogEnvRequirementHandler);
|
||||
register!(env::RegisterEnvironmentHandler);
|
||||
register!(env::GetEnvironmentDetailsHandler);
|
||||
|
||||
register!(workspaces::PinFileHandler);
|
||||
register!(workspaces::UnpinFileHandler);
|
||||
register!(workspaces::ListPinnedFilesHandler);
|
||||
register!(workspaces::StoreSnippetHandler);
|
||||
register!(workspaces::SearchSnippetsHandler);
|
||||
register!(workspaces::DeleteSnippetHandler);
|
||||
register!(workspaces::SaveContextWorkspaceHandler);
|
||||
register!(workspaces::LoadContextWorkspaceHandler);
|
||||
register!(workspaces::ListContextWorkspacesHandler);
|
||||
register!(workspaces::AddPrChecklistItemHandler);
|
||||
register!(workspaces::GetPrChecklistHandler);
|
||||
register!(workspaces::ClearPrChecklistHandler);
|
||||
|
||||
Self { state, tools }
|
||||
}
|
||||
"""
|
||||
|
||||
content = re.sub(struct_pattern, new_struct, content)
|
||||
content = content.replace("impl MemoryHandler {\n pub async fn handle_request", " pub async fn handle_request")
|
||||
|
||||
with open("server/src/handlers.rs", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
print("MemoryHandler struct updated.")
|
||||
@@ -1,45 +0,0 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Create new entities in the knowledge graph.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct CreateEntitiesTool {
|
||||
pub entities: Vec<crate::models::Entity>,
|
||||
}
|
||||
|
||||
/// Create new relations between entities in the knowledge graph.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct CreateRelationsTool {
|
||||
pub relations: Vec<crate::models::Relation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct ObservationInput {
|
||||
#[serde(rename = "entityName")]
|
||||
pub entity_name: String,
|
||||
pub contents: Vec<String>,
|
||||
}
|
||||
|
||||
/// Add new observations to existing entities in the knowledge graph.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct AddObservationsTool {
|
||||
pub observations: Vec<ObservationInput>,
|
||||
}
|
||||
|
||||
/// Define a strict checklist of acceptance criteria for a given task or feature before starting work.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct SetAcceptanceCriteriaTool {
|
||||
/// The name or title of the task/feature being worked on.
|
||||
pub task_title: String,
|
||||
/// An array of specific, undeniable conditions that must be proven before claiming success.
|
||||
pub criteria: Vec<String>,
|
||||
}
|
||||
|
||||
/// Mark a previously defined acceptance criteria as met by providing cryptographic-like proof (logs, output, diffs).
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct VerifyAcceptanceCriteriaTool {
|
||||
/// The exact text of the criteria being met.
|
||||
pub criteria: String,
|
||||
/// The undeniable proof (e.g., test logs, terminal output, git diff) that proves the criteria is met.
|
||||
pub proof: String,
|
||||
}
|
||||
Reference in new issue
Block a user