refactor: drop Manage prefix from consolidated smart MCP tools

This commit is contained in:
Riz Ashraf committed 2026-10-05 11:11:41 +01:00
1 parent e45db33297
commit 961abb01e7
8 files changed
+886 -72

No files matched your search

+50
View File
@@ -162,6 +162,56 @@ impl McpTool for GetEnvironmentDetailsHandler {
} }
} }
pub struct EnvironmentHandler;
#[async_trait]
impl McpTool for EnvironmentHandler {
fn name(&self) -> &'static str {
"environment"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<EnvironmentTool>(
"environment",
"Consolidated environment & infrastructure management",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: EnvironmentTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
match req.action {
EnvAction::UpdateFingerprint => {
let tool_versions = req.tool_versions.unwrap_or_default();
UpdateEnvFingerprintHandler.execute(serde_json::json!({"namespace": ns, "tool_versions": tool_versions}), state).await
}
EnvAction::ReadFingerprint => {
ReadEnvFingerprintHandler.execute(serde_json::json!({"namespace": ns}), state).await
}
EnvAction::LogRequirement => {
let key = req.key.ok_or_else(|| {
crate::error::AppError::Internal("key is required for 'log_requirement' action".to_string())
})?;
let desc = req.description.unwrap_or_default();
let secret = req.is_secret.unwrap_or(false);
LogEnvRequirementHandler.execute(serde_json::json!({"key": key, "description": desc, "is_secret": secret, "namespace": ns}), state).await
}
EnvAction::Register => {
let name = req.name.ok_or_else(|| {
crate::error::AppError::Internal("name is required for 'register' action".to_string())
})?;
let url = req.url.unwrap_or_default();
let desc = req.description.unwrap_or_default();
let vpn = req.requires_vpn.unwrap_or(false);
RegisterEnvironmentHandler.execute(serde_json::json!({"name": name, "url": url, "description": desc, "requires_vpn": vpn, "namespace": ns}), state).await
}
EnvAction::GetDetails => {
GetEnvironmentDetailsHandler.execute(serde_json::json!({"namespace": ns}), state).await
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+103
View File
@@ -524,6 +524,109 @@ impl McpTool for ListTechDebtHandler {
} }
} }
pub struct DecisionsHandler;
#[async_trait]
impl McpTool for DecisionsHandler {
fn name(&self) -> &'static str {
"decisions"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<DecisionsTool>(
"decisions",
"Consolidated Architectural Decision Records (ADRs) management (log, query, delete)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: DecisionsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
match req.action {
DecisionAction::Log => {
let title = req.title.ok_or_else(|| {
crate::error::AppError::Internal("title is required for 'log' action".to_string())
})?;
let status = req.status.unwrap_or_else(|| "accepted".to_string());
let context = req.context.unwrap_or_default();
let decision = req.decision.unwrap_or_default();
let consequences = req.consequences.unwrap_or_default();
LogDecisionHandler.execute(
serde_json::json!({
"title": title,
"status": status,
"context": context,
"decision": decision,
"consequences": consequences,
"namespace": ns
}),
state,
).await
}
DecisionAction::Query => {
QueryDecisionsHandler.execute(serde_json::json!({"query": req.query, "namespace": ns}), state).await
}
DecisionAction::Delete => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("id is required for 'delete' action".to_string())
})?;
DeleteDecisionHandler.execute(serde_json::json!({"id": id}), state).await
}
}
}
}
pub struct TechDebtHandler;
#[async_trait]
impl McpTool for TechDebtHandler {
fn name(&self) -> &'static str {
"tech_debt"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<TechDebtTool>(
"tech_debt",
"Consolidated technical debt management (log, resolve, list)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: TechDebtTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
match req.action {
TechDebtAction::Log => {
let desc = req.description.ok_or_else(|| {
crate::error::AppError::Internal("description is required for 'log' action".to_string())
})?;
let ideal = req.ideal_solution.unwrap_or_default();
LogTechDebtHandler.execute(
serde_json::json!({
"description": desc,
"ideal_solution": ideal,
"git_commit": req.git_commit,
"git_branch": req.git_branch,
"symbol_references": req.symbol_references,
"line_range": req.line_range,
"namespace": ns
}),
state,
).await
}
TechDebtAction::Resolve => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("id is required for 'resolve' action".to_string())
})?;
ResolveTechDebtHandler.execute(serde_json::json!({"id": id}), state).await
}
TechDebtAction::List => {
let inc = req.include_resolved.unwrap_or(false);
ListTechDebtHandler.execute(serde_json::json!({"include_resolved": inc, "namespace": ns}), state).await
}
}
}
}
pub struct OmniSearchHandler; pub struct OmniSearchHandler;
#[async_trait] #[async_trait]
+40 -66
View File
@@ -7,23 +7,23 @@ use serde_json::Value;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
pub struct ManageStickyNotesHandler; pub struct StickyNotesHandler;
#[async_trait] #[async_trait]
impl McpTool for ManageStickyNotesHandler { impl McpTool for StickyNotesHandler {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"manage_sticky_notes" "sticky_notes"
} }
fn schema(&self) -> Value { fn schema(&self) -> Value {
crate::mcp::tool_def::<ManageStickyNotesTool>( crate::mcp::tool_def::<StickyNotesTool>(
"manage_sticky_notes", "sticky_notes",
"Manage ephemeral sticky notes with TTL (add, read, delete, clear)", "Ephemeral sticky notes with TTL (add, read, delete, clear)",
) )
} }
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> { async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ManageStickyNotesTool = serde_json::from_value(args).map_err(|e| e.to_string())?; let req: StickyNotesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
match req.action { match req.action {
StickyNoteAction::Add => { StickyNoteAction::Add => {
let content = req.content.ok_or_else(|| { let content = req.content.ok_or_else(|| {
@@ -216,29 +216,35 @@ impl McpTool for ClearStickyNotesHandler {
} }
} }
pub struct LeaveHandoffMemoHandler; pub struct HandoffMemosHandler;
#[async_trait] #[async_trait]
impl McpTool for LeaveHandoffMemoHandler { impl McpTool for HandoffMemosHandler {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"leave_handoff_memo" "handoff_memos"
} }
fn schema(&self) -> Value { fn schema(&self) -> Value {
crate::mcp::tool_def::<LeaveHandoffMemoTool>( crate::mcp::tool_def::<HandoffMemosTool>(
"leave_handoff_memo", "handoff_memos",
"Execute leave_handoff_memo", "Handoff memos across sessions (leave, read, clear)",
) )
} }
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> { async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: LeaveHandoffMemoTool = serde_json::from_value(args).map_err(|e| e.to_string())?; let req: HandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
match req.action {
HandoffMemoAction::Leave => {
let content = req.content.ok_or_else(|| {
crate::error::AppError::Internal("content is required for 'leave' action".to_string())
})?;
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
state.telemetry.handoff_memos.modify(|memos| { state.telemetry.handoff_memos.modify(|memos| {
memos.push(crate::models::HandoffMemo { memos.push(crate::models::HandoffMemo {
id: uuid::Uuid::new_v4().to_string(), id: uuid::Uuid::new_v4().to_string(),
author: "agy".to_string(), author: "agy".to_string(),
content: req.content, content,
namespace: req.namespace, namespace: ns,
timestamp: crate::handlers::utils::now_secs(), timestamp: crate::handlers::utils::now_secs(),
expires_at: None, expires_at: None,
}); });
@@ -246,27 +252,9 @@ impl McpTool for LeaveHandoffMemoHandler {
memos.remove(0); memos.remove(0);
} }
}); });
Ok("Handoff memo left".to_string()) Ok("Handoff memo left.".to_string())
} }
} HandoffMemoAction::Read => {
pub struct ReadHandoffMemosHandler;
#[async_trait]
impl McpTool for ReadHandoffMemosHandler {
fn name(&self) -> &'static str {
"read_handoff_memos"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<ReadHandoffMemosTool>(
"read_handoff_memos",
"Execute read_handoff_memos",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ReadHandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let data = state.telemetry.handoff_memos.read_with(|items| { let data = state.telemetry.handoff_memos.read_with(|items| {
let filtered: Vec<_> = items let filtered: Vec<_> = items
.iter() .iter()
@@ -282,31 +270,18 @@ impl McpTool for ReadHandoffMemosHandler {
})?; })?;
Ok(data) Ok(data)
} }
} HandoffMemoAction::Clear => {
let ids: HashSet<_> = req.ids.unwrap_or_default().into_iter().collect();
pub struct ClearHandoffMemosHandler; state.telemetry.handoff_memos.modify(|memos| {
if ids.is_empty() {
#[async_trait] memos.clear();
impl McpTool for ClearHandoffMemosHandler { } else {
fn name(&self) -> &'static str { memos.retain(|m| !ids.contains(&m.id));
"clear_handoff_memos" }
});
Ok("Handoff memos cleared.".to_string())
} }
fn schema(&self) -> Value {
crate::mcp::tool_def::<ClearHandoffMemosTool>(
"clear_handoff_memos",
"Execute clear_handoff_memos",
)
} }
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ClearHandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let ids: HashSet<_> = req.ids.into_iter().collect();
state
.telemetry
.handoff_memos
.modify(|memos| memos.retain(|m| !ids.contains(&m.id)));
Ok("Handoff memos cleared".to_string())
} }
} }
@@ -481,10 +456,10 @@ mod tests {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap())); let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
let handoff_handler = LeaveHandoffMemoHandler; let handoff_handler = HandoffMemosHandler;
let args = json!({ let args = json!({
"action": "leave",
"content": "Finished implementing graph tests", "content": "Finished implementing graph tests",
"author": "Antigravity",
"namespace": "global" "namespace": "global"
}); });
@@ -493,11 +468,10 @@ mod tests {
.await .await
.map_err(|e| crate::error::AppError::Internal(e.to_string())) .map_err(|e| crate::error::AppError::Internal(e.to_string()))
.unwrap(); .unwrap();
assert_eq!(res, "Handoff memo left"); assert_eq!(res, "Handoff memo left.");
let read_handoff = ReadHandoffMemosHandler; let res2 = handoff_handler
let res2 = read_handoff .execute(json!({"action": "read", "namespace": "global"}), state.clone())
.execute(json!({"namespace": "global"}), state.clone())
.await .await
.map_err(|e| crate::error::AppError::Internal(e.to_string())) .map_err(|e| crate::error::AppError::Internal(e.to_string()))
.unwrap(); .unwrap();
@@ -532,7 +506,7 @@ mod tests {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap())); let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
let handler = ManageStickyNotesHandler; let handler = StickyNotesHandler;
let invalid_args = serde_json::json!({ let invalid_args = serde_json::json!({
"action": "invalid_action" "action": "invalid_action"
}); });
+139
View File
@@ -512,6 +512,145 @@ impl McpTool for ListMilestonesHandler {
} }
} }
pub struct TasksHandler;
#[async_trait]
impl McpTool for TasksHandler {
fn name(&self) -> &'static str {
"tasks"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<TasksTool>(
"tasks",
"Consolidated task management (add, update, delete, list, set_criteria, verify)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: TasksTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
match req.action {
TaskAction::Add => {
let title = req.title.ok_or_else(|| {
crate::error::AppError::Internal("title is required for 'add' action".to_string())
})?;
let description = req.description.unwrap_or_default();
let add_args = serde_json::json!({
"title": title,
"description": description,
"git_branch": req.git_branch,
"parent_id": req.parent_id,
"dependencies": req.dependencies,
});
AddTaskHandler.execute(add_args, state).await
}
TaskAction::Update => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("id is required for 'update' action".to_string())
})?;
let status = req.status.ok_or_else(|| {
crate::error::AppError::Internal("status is required for 'update' action".to_string())
})?;
let update_args = serde_json::json!({
"id": id,
"status": status,
});
UpdateTaskStatusHandler.execute(update_args, state).await
}
TaskAction::Delete => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("id is required for 'delete' action".to_string())
})?;
let del_args = serde_json::json!({
"id": id,
});
DeleteTaskHandler.execute(del_args, state).await
}
TaskAction::List => {
let list_args = serde_json::json!({
"git_branch": req.git_branch,
"summary_level": req.summary_level,
"max_tokens": req.max_tokens,
});
ListActiveTasksHandler.execute(list_args, state).await
}
TaskAction::SetCriteria => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("id is required for 'set_criteria' action".to_string())
})?;
let criteria = req.criteria.ok_or_else(|| {
crate::error::AppError::Internal("criteria array is required for 'set_criteria' action".to_string())
})?;
let set_args = serde_json::json!({
"id": id,
"acceptance_criteria": criteria,
});
SetAcceptanceCriteriaHandler.execute(set_args, state).await
}
TaskAction::Verify => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("id is required for 'verify' action".to_string())
})?;
let verify_args = serde_json::json!({
"id": id,
});
VerifyAcceptanceCriteriaHandler.execute(verify_args, state).await
}
}
}
}
pub struct MilestonesHandler;
#[async_trait]
impl McpTool for MilestonesHandler {
fn name(&self) -> &'static str {
"milestones"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<MilestonesTool>(
"milestones",
"Consolidated milestone management (add, update, list)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: MilestonesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
match req.action {
MilestoneAction::Add => {
let title = req.title.ok_or_else(|| {
crate::error::AppError::Internal("title is required for 'add' action".to_string())
})?;
let add_args = serde_json::json!({
"title": title,
"namespace": req.namespace.unwrap_or_else(|| crate::models::default_namespace()),
});
AddMilestoneHandler.execute(add_args, state).await
}
MilestoneAction::Update => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("id is required for 'update' action".to_string())
})?;
let status = req.status.ok_or_else(|| {
crate::error::AppError::Internal("status is required for 'update' action".to_string())
})?;
let update_args = serde_json::json!({
"id": id,
"status": status,
});
UpdateMilestoneHandler.execute(update_args, state).await
}
MilestoneAction::List => {
let list_args = serde_json::json!({
"namespace": req.namespace,
});
ListMilestonesHandler.execute(list_args, state).await
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+42
View File
@@ -218,6 +218,48 @@ impl McpTool for ToggleClipboardWatchModeHandler {
} }
} }
use crate::tools::{ClipboardAction, ClipboardTool};
pub struct ClipboardHandler;
#[async_trait]
impl McpTool for ClipboardHandler {
fn name(&self) -> &'static str {
"clipboard"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<ClipboardTool>(
"clipboard",
"Consolidated OS clipboard management (read, write, toggle_watch)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ClipboardTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
match req.action {
ClipboardAction::Read => {
ReadClipboardHandler.execute(serde_json::json!({}), state).await
}
ClipboardAction::Write => {
WriteClipboardHandler.execute(
serde_json::json!({
"text": req.text,
"html": req.html,
"files": req.files,
"image_path": req.image_path
}),
state,
).await
}
ClipboardAction::ToggleWatch => {
let enable = req.enabled.unwrap_or(true);
ToggleClipboardWatchModeHandler.execute(serde_json::json!({"enable": enable}), state).await
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+197
View File
@@ -475,6 +475,203 @@ impl McpTool for ClearPrChecklistHandler {
} }
} }
pub struct PinnedFilesHandler;
#[async_trait]
impl McpTool for PinnedFilesHandler {
fn name(&self) -> &'static str {
"pinned_files"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<PinnedFilesTool>(
"pinned_files",
"Consolidated pinned files focus management (pin, unpin, list)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: PinnedFilesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
match req.action {
PinnedFileAction::Pin => {
let path = req.path.ok_or_else(|| {
crate::error::AppError::Internal("path is required for 'pin' action".to_string())
})?;
PinFileHandler.execute(serde_json::json!({"file_path": path, "namespace": ns}), state).await
}
PinnedFileAction::Unpin => {
let path = req.path.ok_or_else(|| {
crate::error::AppError::Internal("path is required for 'unpin' action".to_string())
})?;
UnpinFileHandler.execute(serde_json::json!({"file_path": path, "namespace": ns}), state).await
}
PinnedFileAction::List => {
ListPinnedFilesHandler.execute(serde_json::json!({"namespace": ns}), state).await
}
}
}
}
pub struct ContextWorkspacesHandler;
#[async_trait]
impl McpTool for ContextWorkspacesHandler {
fn name(&self) -> &'static str {
"context_workspaces"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<ContextWorkspacesTool>(
"context_workspaces",
"Consolidated context workspace state management (save, load, list, delete, diff)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: ContextWorkspacesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
match req.action {
ContextWorkspaceAction::Save => {
let name = req.name.ok_or_else(|| {
crate::error::AppError::Internal("name is required for 'save' action".to_string())
})?;
let pinned_files = req.pinned_files.unwrap_or_default();
let active_task_ids = req.active_task_ids.unwrap_or_default();
SaveContextWorkspaceHandler.execute(
serde_json::json!({
"name": name,
"namespace": ns,
"pinned_files": pinned_files,
"active_task_ids": active_task_ids
}),
state,
).await
}
ContextWorkspaceAction::Load => {
let name = req.name.ok_or_else(|| {
crate::error::AppError::Internal("name is required for 'load' action".to_string())
})?;
LoadContextWorkspaceHandler.execute(serde_json::json!({"name": name, "namespace": ns}), state).await
}
ContextWorkspaceAction::List => {
ListContextWorkspacesHandler.execute(serde_json::json!({"namespace": ns}), state).await
}
ContextWorkspaceAction::Delete => {
let name = req.name.ok_or_else(|| {
crate::error::AppError::Internal("name is required for 'delete' action".to_string())
})?;
DeleteContextWorkspaceHandler.execute(serde_json::json!({"name": name, "namespace": ns}), state).await
}
ContextWorkspaceAction::Diff => {
let name = req.name.ok_or_else(|| {
crate::error::AppError::Internal("name is required for 'diff' action".to_string())
})?;
let other = req.other_name.ok_or_else(|| {
crate::error::AppError::Internal("other_name is required for 'diff' action".to_string())
})?;
DiffContextWorkspacesHandler.execute(serde_json::json!({"workspace_a": name, "workspace_b": other, "namespace": ns}), state).await
}
}
}
}
pub struct PrChecklistHandler;
#[async_trait]
impl McpTool for PrChecklistHandler {
fn name(&self) -> &'static str {
"pr_checklist"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<PrChecklistTool>(
"pr_checklist",
"Consolidated PR checklist management (add, get, clear)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: PrChecklistTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let ns = req.namespace.unwrap_or_else(|| crate::models::default_namespace());
match req.action {
PrChecklistAction::Add => {
let desc = req.description.ok_or_else(|| {
crate::error::AppError::Internal("description is required for 'add' action".to_string())
})?;
AddPrChecklistItemHandler.execute(serde_json::json!({"description": desc, "namespace": ns}), state).await
}
PrChecklistAction::Get => {
GetPrChecklistHandler.execute(serde_json::json!({"namespace": ns}), state).await
}
PrChecklistAction::Clear => {
ClearPrChecklistHandler.execute(serde_json::json!({"namespace": ns}), state).await
}
}
}
}
pub struct SnippetsHandler;
#[async_trait]
impl McpTool for SnippetsHandler {
fn name(&self) -> &'static str {
"snippets"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<SnippetsTool>(
"snippets",
"Consolidated reusable code snippets management (store, search, delete, tag)",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: SnippetsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
match req.action {
SnippetAction::Store => {
let name = req.query.or(req.id).ok_or_else(|| {
crate::error::AppError::Internal("name is required for 'store' action".to_string())
})?;
let lang = req.language.unwrap_or_else(|| "text".to_string());
let code = req.code.unwrap_or_default();
let desc = req.description.unwrap_or_default();
StoreSnippetHandler.execute(
serde_json::json!({
"name": name,
"language": lang,
"code": code,
"description": desc,
"tags": req.tags
}),
state,
).await
}
SnippetAction::Search => {
let q = req.query.unwrap_or_default();
if req.hybrid.unwrap_or(false) {
crate::handlers::meta::SearchSnippetsHybridHandler.execute(serde_json::json!({"query": q, "tags": req.tags}), state).await
} else {
SearchSnippetsHandler.execute(serde_json::json!({"query": q, "tags": req.tags}), state).await
}
}
SnippetAction::Delete => {
let id = req.id.or(req.query).ok_or_else(|| {
crate::error::AppError::Internal("id/name is required for 'delete' action".to_string())
})?;
DeleteSnippetHandler.execute(serde_json::json!({"name": id}), state).await
}
SnippetAction::Tag => {
let id = req.id.or(req.query).ok_or_else(|| {
crate::error::AppError::Internal("id/name is required for 'tag' action".to_string())
})?;
let tags = req.tags.unwrap_or_default();
TagSnippetHandler.execute(serde_json::json!({"name": id, "tags": tags}), state).await
}
}
}
}
use crate::handlers::utils::*; use crate::handlers::utils::*;
#[cfg(test)] #[cfg(test)]
+12 -4
View File
@@ -493,6 +493,8 @@ impl MemoryHandler {
register!(graph::MergeEntitiesHandler); register!(graph::MergeEntitiesHandler);
register!(graph::FindOrphansHandler); register!(graph::FindOrphansHandler);
register!(tasks::TasksHandler);
register!(tasks::MilestonesHandler);
register!(tasks::AddTaskHandler); register!(tasks::AddTaskHandler);
register!(tasks::DeleteTaskHandler); register!(tasks::DeleteTaskHandler);
register!(tasks::UpdateTaskStatusHandler); register!(tasks::UpdateTaskStatusHandler);
@@ -503,18 +505,18 @@ impl MemoryHandler {
register!(tasks::UpdateMilestoneHandler); register!(tasks::UpdateMilestoneHandler);
register!(tasks::ListMilestonesHandler); register!(tasks::ListMilestonesHandler);
register!(notes::ManageStickyNotesHandler); register!(notes::StickyNotesHandler);
register!(notes::HandoffMemosHandler);
register!(notes::AddStickyNoteHandler); register!(notes::AddStickyNoteHandler);
register!(notes::ReadStickyNotesHandler); register!(notes::ReadStickyNotesHandler);
register!(notes::DeleteStickyNoteHandler); register!(notes::DeleteStickyNoteHandler);
register!(notes::ClearStickyNotesHandler); register!(notes::ClearStickyNotesHandler);
register!(notes::LeaveHandoffMemoHandler);
register!(notes::ReadHandoffMemosHandler);
register!(notes::ClearHandoffMemosHandler);
register!(notes::AddSessionSummaryHandler); register!(notes::AddSessionSummaryHandler);
register!(notes::GenerateStandupReportHandler); register!(notes::GenerateStandupReportHandler);
register!(notes::PromoteToEntityHandler); register!(notes::PromoteToEntityHandler);
register!(meta::DecisionsHandler);
register!(meta::TechDebtHandler);
register!(meta::LogDecisionHandler); register!(meta::LogDecisionHandler);
register!(meta::QueryDecisionsHandler); register!(meta::QueryDecisionsHandler);
register!(meta::DeleteDecisionHandler); register!(meta::DeleteDecisionHandler);
@@ -530,12 +532,17 @@ impl MemoryHandler {
register!(meta::OmniSearchHandler); register!(meta::OmniSearchHandler);
register!(meta::GetProjectHealthHandler); register!(meta::GetProjectHealthHandler);
register!(env::EnvironmentHandler);
register!(env::UpdateEnvFingerprintHandler); register!(env::UpdateEnvFingerprintHandler);
register!(env::ReadEnvFingerprintHandler); register!(env::ReadEnvFingerprintHandler);
register!(env::LogEnvRequirementHandler); register!(env::LogEnvRequirementHandler);
register!(env::RegisterEnvironmentHandler); register!(env::RegisterEnvironmentHandler);
register!(env::GetEnvironmentDetailsHandler); register!(env::GetEnvironmentDetailsHandler);
register!(workspaces::PinnedFilesHandler);
register!(workspaces::ContextWorkspacesHandler);
register!(workspaces::PrChecklistHandler);
register!(workspaces::SnippetsHandler);
register!(workspaces::PinFileHandler); register!(workspaces::PinFileHandler);
register!(workspaces::UnpinFileHandler); register!(workspaces::UnpinFileHandler);
register!(workspaces::ListPinnedFilesHandler); register!(workspaces::ListPinnedFilesHandler);
@@ -549,6 +556,7 @@ impl MemoryHandler {
register!(workspaces::AddPrChecklistItemHandler); register!(workspaces::AddPrChecklistItemHandler);
register!(workspaces::GetPrChecklistHandler); register!(workspaces::GetPrChecklistHandler);
register!(workspaces::ClearPrChecklistHandler); register!(workspaces::ClearPrChecklistHandler);
register!(vision::ClipboardHandler);
register!(vision::ReadClipboardHandler); register!(vision::ReadClipboardHandler);
register!(vision::WriteClipboardHandler); register!(vision::WriteClipboardHandler);
register!(git::GetActiveWorktreeContextHandler); register!(git::GetActiveWorktreeContextHandler);
+303 -2
View File
@@ -140,9 +140,9 @@ pub enum StickyNoteAction {
Clear, Clear,
} }
/// Manage ephemeral sticky notes with TTL (add, read, delete, clear). /// Ephemeral sticky notes with TTL (add, read, delete, clear).
#[derive(Debug, Deserialize, Serialize, JsonSchema)] #[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ManageStickyNotesTool { pub struct StickyNotesTool {
/// Action to perform: 'add', 'read', 'delete', or 'clear'. /// Action to perform: 'add', 'read', 'delete', or 'clear'.
pub action: StickyNoteAction, pub action: StickyNoteAction,
/// Content of the sticky note (required for 'add'). /// Content of the sticky note (required for 'add').
@@ -1083,3 +1083,304 @@ pub struct SearchSnippetsHybridTool {
pub limit: Option<usize>, pub limit: Option<usize>,
} }
// Consolidated Smart Management Tools
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum HandoffMemoAction {
Leave,
Read,
Clear,
}
/// Manage handoff memos across sessions (leave, read, clear).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct HandoffMemosTool {
/// Action to perform: 'leave', 'read', or 'clear'.
pub action: HandoffMemoAction,
/// Content of the handoff memo (required for 'leave').
pub content: Option<String>,
/// Optional namespace filter or assignment.
pub namespace: Option<String>,
/// Array of memo IDs to clear (required for 'clear').
pub ids: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum TaskAction {
Add,
Update,
Delete,
List,
SetCriteria,
Verify,
}
/// Consolidated task management (add, update, delete, list, set_criteria, verify).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct TasksTool {
/// Action to perform: 'add', 'update', 'delete', 'list', 'set_criteria', or 'verify'.
pub action: TaskAction,
/// Task ID (required for 'update', 'delete', 'set_criteria', 'verify').
pub id: Option<String>,
/// Task title (required for 'add').
pub title: Option<String>,
/// Task description (required for 'add').
pub description: Option<String>,
/// New status: 'pending', 'completed', or 'cancelled' (for 'update').
pub status: Option<String>,
/// Parent task ID (optional for 'add').
pub parent_id: Option<String>,
/// List of dependency task IDs (optional for 'add').
pub dependencies: Option<Vec<String>>,
/// Git branch filter or assignment.
pub git_branch: Option<String>,
/// Acceptance criteria (required for 'set_criteria').
pub criteria: Option<Vec<String>>,
/// Summary level: 'compact', 'detailed', or 'full' (for 'list').
pub summary_level: Option<String>,
/// Maximum tokens budget cap (for 'list').
pub max_tokens: Option<usize>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum MilestoneAction {
Add,
Update,
List,
}
/// Consolidated milestone management (add, update, list).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct MilestonesTool {
/// Action to perform: 'add', 'update', or 'list'.
pub action: MilestoneAction,
/// Milestone ID (required for 'update').
pub id: Option<String>,
/// Milestone title (required for 'add').
pub title: Option<String>,
/// New status (for 'update').
pub status: Option<String>,
/// Optional namespace filter or assignment.
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PinnedFileAction {
Pin,
Unpin,
List,
}
/// Consolidated pinned files focus management (pin, unpin, list).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct PinnedFilesTool {
/// Action to perform: 'pin', 'unpin', or 'list'.
pub action: PinnedFileAction,
/// Absolute or workspace relative file path (required for 'pin' and 'unpin').
pub path: Option<String>,
/// Optional namespace.
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ContextWorkspaceAction {
Save,
Load,
List,
Delete,
Diff,
}
/// Consolidated context workspace state management (save, load, list, delete, diff).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ContextWorkspacesTool {
/// Action to perform: 'save', 'load', 'list', 'delete', or 'diff'.
pub action: ContextWorkspaceAction,
/// Workspace name.
pub name: Option<String>,
/// Second workspace name (for 'diff').
pub other_name: Option<String>,
/// Pinned file paths (for 'save').
pub pinned_files: Option<Vec<String>>,
/// Active task IDs (for 'save').
pub active_task_ids: Option<Vec<String>>,
/// Optional namespace.
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PrChecklistAction {
Add,
Get,
Clear,
}
/// Consolidated PR checklist management (add, get, clear).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct PrChecklistTool {
/// Action to perform: 'add', 'get', or 'clear'.
pub action: PrChecklistAction,
/// Checklist item description (required for 'add').
pub description: Option<String>,
/// Optional namespace.
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum SnippetAction {
Store,
Search,
Delete,
Tag,
}
/// Consolidated reusable code snippets management (store, search, delete, tag).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SnippetsTool {
/// Action to perform: 'store', 'search', 'delete', or 'tag'.
pub action: SnippetAction,
/// Snippet ID (required for 'delete', 'tag').
pub id: Option<String>,
/// Search query or snippet name.
pub query: Option<String>,
/// Snippet language (for 'store').
pub language: Option<String>,
/// Code content (for 'store').
pub code: Option<String>,
/// Snippet description (for 'store').
pub description: Option<String>,
/// Classification tags (for 'store', 'search', 'tag').
pub tags: Option<Vec<String>>,
/// Enable hybrid lexical + semantic vector ranking (for 'search').
pub hybrid: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum DecisionAction {
Log,
Query,
Delete,
}
/// Consolidated Architectural Decision Records (ADRs) management (log, query, delete).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct DecisionsTool {
/// Action to perform: 'log', 'query', or 'delete'.
pub action: DecisionAction,
/// ADR ID (required for 'delete').
pub id: Option<String>,
/// ADR title (required for 'log').
pub title: Option<String>,
/// ADR status (required for 'log').
pub status: Option<String>,
/// Context & problem statement (required for 'log').
pub context: Option<String>,
/// Decision made (required for 'log').
pub decision: Option<String>,
/// Consequences & tradeoffs (required for 'log').
pub consequences: Option<String>,
/// Search query string (for 'query').
pub query: Option<String>,
/// Optional namespace.
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum TechDebtAction {
Log,
Resolve,
List,
}
/// Consolidated technical debt management (log, resolve, list).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct TechDebtTool {
/// Action to perform: 'log', 'resolve', or 'list'.
pub action: TechDebtAction,
/// Tech debt ID (required for 'resolve').
pub id: Option<String>,
/// Tech debt description (required for 'log').
pub description: Option<String>,
/// Ideal solution (required for 'log').
pub ideal_solution: Option<String>,
/// Include resolved tech debt items (for 'list').
pub include_resolved: Option<bool>,
/// Associated git commit hash.
pub git_commit: Option<String>,
/// Associated git branch.
pub git_branch: Option<String>,
/// Symbol references associated with the tech debt.
pub symbol_references: Option<Vec<String>>,
/// Line range associated with the tech debt.
pub line_range: Option<String>,
/// Optional namespace.
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum EnvAction {
UpdateFingerprint,
ReadFingerprint,
LogRequirement,
Register,
GetDetails,
}
/// Consolidated environment & infrastructure management (update_fingerprint, read_fingerprint, log_requirement, register, get_details).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct EnvironmentTool {
/// Action to perform.
pub action: EnvAction,
/// Map of tool names to versions (for 'update_fingerprint').
pub tool_versions: Option<std::collections::HashMap<String, String>>,
/// Env variable key (for 'log_requirement').
pub key: Option<String>,
/// Description (for 'log_requirement', 'register').
pub description: Option<String>,
/// Secret flag (for 'log_requirement').
pub is_secret: Option<bool>,
/// Environment name (for 'register').
pub name: Option<String>,
/// Environment URL (for 'register').
pub url: Option<String>,
/// VPN required flag (for 'register').
pub requires_vpn: Option<bool>,
/// Optional namespace.
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ClipboardAction {
Read,
Write,
ToggleWatch,
}
/// Consolidated OS clipboard management (read, write, toggle_watch).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ClipboardTool {
/// Action to perform: 'read', 'write', or 'toggle_watch'.
pub action: ClipboardAction,
/// Plain text content (for 'write').
pub text: Option<String>,
/// HTML content (for 'write').
pub html: Option<String>,
/// File paths for Windows File Drop (for 'write').
pub files: Option<Vec<String>>,
/// Image path (for 'write').
pub image_path: Option<String>,
/// Enable watch mode flag (for 'toggle_watch').
pub enabled: Option<bool>,
}