Compare commits
4
Commits
4bae4e9c08
...
01d3c22c57
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01d3c22c57 | ||
|
|
48a1973ea6 | ||
|
|
1ea635cdcc | ||
|
|
b7d1ae78db |
No files matched your search
@@ -478,6 +478,12 @@
|
||||
<button class="tab-button" onclick="switchTab('search-tab', this)">Omni-Search</button>
|
||||
<button class="tab-button" onclick="switchTab('task-tab', this)">Task Board</button>
|
||||
<button class="tab-button" onclick="switchTab('sticky-tab', this)">Sticky Notes</button>
|
||||
<button class="tab-button" onclick="switchTab('techdebt-tab', this)">Tech Debt</button>
|
||||
<button class="tab-button" onclick="switchTab('adrs-tab', this)">Decisions (ADR)</button>
|
||||
<button class="tab-button" onclick="switchTab('workspaces-tab', this)">Workspaces</button>
|
||||
<button class="tab-button" onclick="switchTab('memos-tab', this)">Handoffs</button>
|
||||
<button class="tab-button" onclick="switchTab('snippets-tab', this)">Snippets</button>
|
||||
<button class="tab-button" onclick="switchTab('pr-tab', this)">PRs</button>
|
||||
<button class="tab-button" onclick="switchTab('activity-tab', this)">Live Activity</button>
|
||||
</div>
|
||||
|
||||
@@ -529,6 +535,50 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="techdebt-tab" class="tab-content">
|
||||
<div class="panel" style="flex:1; display:flex; flex-direction:column; overflow-y:auto;">
|
||||
<h2>Tech Debt</h2>
|
||||
<div id="techdebt-container" style="display:flex; flex-direction:column; gap:15px; margin-bottom: 20px;"></div>
|
||||
<h2 style="border-top: 1px solid var(--border-color); padding-top: 15px;">Resolved Error Patterns</h2>
|
||||
<div id="errorfixes-container" style="display:flex; flex-direction:column; gap:15px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="adrs-tab" class="tab-content">
|
||||
<div class="panel" style="flex:1; display:flex; flex-direction:column; overflow-y:auto;">
|
||||
<h2>Architecture Decision Records</h2>
|
||||
<div id="adrs-container" style="display:flex; flex-direction:column; gap:15px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="workspaces-tab" class="tab-content">
|
||||
<div class="panel" style="flex:1; display:flex; flex-direction:column; overflow-y:auto;">
|
||||
<h2>Context Workspaces</h2>
|
||||
<div id="workspaces-container" style="display:flex; flex-direction:column; gap:15px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="memos-tab" class="tab-content">
|
||||
<div class="panel" style="flex:1; display:flex; flex-direction:column; overflow-y:auto;">
|
||||
<h2>Handoff Memos & Milestones</h2>
|
||||
<div id="memos-container" style="display:flex; flex-direction:column; gap:15px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="snippets-tab" class="tab-content">
|
||||
<div class="panel" style="flex:1; display:flex; flex-direction:column; overflow-y:auto;">
|
||||
<h2>Snippets Library</h2>
|
||||
<div id="snippets-container" style="display:flex; flex-direction:column; gap:15px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pr-tab" class="tab-content">
|
||||
<div class="panel" style="flex:1; display:flex; flex-direction:column; overflow-y:auto;">
|
||||
<h2>PR Checklists</h2>
|
||||
<div id="pr-container" style="display:flex; flex-direction:column; gap:15px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="activity-tab" class="tab-content">
|
||||
<div class="panel activity-panel" style="flex:1; display:flex; flex-direction:column;">
|
||||
<div id="activity-feed" style="flex:1;"></div>
|
||||
@@ -549,9 +599,16 @@
|
||||
// Resize graph when becoming visible again
|
||||
network.redraw();
|
||||
network.fit();
|
||||
loadGraph();
|
||||
} else if (tabId === 'activity-tab') {
|
||||
const feed = document.getElementById('activity-feed');
|
||||
feed.scrollTop = feed.scrollHeight;
|
||||
} else if (tabId === 'task-tab') {
|
||||
loadTasks();
|
||||
} else if (tabId === 'sticky-tab') {
|
||||
loadStickyNotes();
|
||||
} else {
|
||||
loadAllExtras();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,6 +1043,82 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGenericList(endpoint, containerId, formatter) {
|
||||
try {
|
||||
const res = await fetch(endpoint);
|
||||
const data = await res.json();
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
container.innerHTML = '<div style="color:var(--text-secondary); padding:20px; text-align:center;">No items recorded.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = data.map(item => {
|
||||
const date = item.timestamp ? new Date(item.timestamp * 1000).toLocaleString() : '';
|
||||
return `<div class="task-card" style="border-left-color: #9b59b6;">
|
||||
<div style="font-size:0.75em; color:var(--text-secondary); float:right;">${date}</div>
|
||||
${formatter(item)}
|
||||
</div>`;
|
||||
}).join('');
|
||||
} catch (err) {
|
||||
console.error(`Failed to load ${endpoint}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
function loadAllExtras() {
|
||||
loadGenericList('/api/tech_debts', 'techdebt-container', item => `
|
||||
<strong>${item.title}</strong>
|
||||
<div>${item.description}</div>
|
||||
<div style="margin-top:10px;">
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; margin-right:8px; border:1px solid var(--border-color);">Severity: ${item.severity || 'Medium'}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color);">Status: ${item.status || 'open'}</span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
loadGenericList('/api/error_fixes', 'errorfixes-container', item => `
|
||||
<strong>${item.error_message || 'Error'}</strong>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Fix:</em> ${item.fix_description || ''}</div>
|
||||
<div style="font-family:monospace; font-size:0.85em; color:#e74c3c;">Files: ${(item.files_changed || []).join(', ')}</div>
|
||||
`);
|
||||
|
||||
loadGenericList('/api/adrs', 'adrs-container', item => `
|
||||
<strong>${item.title}</strong>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Context:</em> ${item.context || ''}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Decision:</em> ${item.decision || ''}</div>
|
||||
<div style="font-size:0.9em;"><em>Consequences:</em> ${item.consequences || ''}</div>
|
||||
`);
|
||||
|
||||
loadGenericList('/api/context_workspaces', 'workspaces-container', item => `
|
||||
<strong>${item.name}</strong>
|
||||
<div style="margin-bottom:8px;">${item.description || ''}</div>
|
||||
<div style="font-family:monospace; font-size:0.85em; color:#3498db;">${(item.paths || []).join(', ')}</div>
|
||||
`);
|
||||
|
||||
loadGenericList('/api/handoff_memos', 'memos-container', item => `
|
||||
<strong>Memo from ${item.author || 'System'}</strong>
|
||||
<div style="white-space:pre-wrap; font-size:0.9em; line-height:1.4;">${item.content || item.summary || ''}</div>
|
||||
`);
|
||||
|
||||
loadGenericList('/api/snippets', 'snippets-container', item => `
|
||||
<strong>${item.description || 'Snippet'}</strong>
|
||||
<div style="margin-bottom:8px; font-size:0.8em;">Language: ${item.language || 'txt'} | Tags: ${(item.tags || []).join(', ')}</div>
|
||||
<pre style="background:#111417; color:#d1d8e0; padding:10px; border-radius:4px; overflow-x:auto; font-size:0.85em;">${item.content || item.code || ''}</pre>
|
||||
`);
|
||||
|
||||
loadGenericList('/api/pr_checklists', 'pr-container', item => `
|
||||
<strong>${item.name || 'Checklist'}</strong>
|
||||
<ul style="padding-left:20px; font-size:0.9em;">
|
||||
${(item.items || []).map(i => {
|
||||
const check = i.is_completed ? '☑' : '☐';
|
||||
const strike = i.is_completed ? 'text-decoration:line-through; color:var(--text-secondary);' : '';
|
||||
return `<li style="${strike}">${check} ${i.description}</li>`;
|
||||
}).join('')}
|
||||
</ul>
|
||||
`);
|
||||
}
|
||||
|
||||
async function loadVersion() {
|
||||
try {
|
||||
const res = await fetch('/api/version');
|
||||
@@ -1005,6 +1138,7 @@
|
||||
loadTasks();
|
||||
loadStickyNotes();
|
||||
loadActivityHistory();
|
||||
loadAllExtras();
|
||||
setupWS();
|
||||
|
||||
// Listen to theme toggle changes to update graph font colors
|
||||
@@ -1015,6 +1149,7 @@
|
||||
loadGraph();
|
||||
loadTasks();
|
||||
loadStickyNotes();
|
||||
loadAllExtras();
|
||||
}, 5000);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -62,7 +62,7 @@ impl McpTool for ReadEnvFingerprintHandler {
|
||||
.read_with(|fps| fps.get(&req.namespace).cloned());
|
||||
if let Some(fp) = data {
|
||||
let data = serde_json::to_string(&fp).unwrap_or_default();
|
||||
Ok(data.to_string())
|
||||
Ok(data)
|
||||
} else {
|
||||
Ok("{}".to_string())
|
||||
}
|
||||
@@ -155,9 +155,9 @@ impl McpTool for GetEnvironmentDetailsHandler {
|
||||
.iter()
|
||||
.filter(|e| e.namespace == req.namespace)
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -380,8 +380,8 @@ impl McpTool for SearchNodesHandler {
|
||||
result.entities.insert(id, e);
|
||||
}
|
||||
}
|
||||
serde_json::to_string(&result).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_string(&result).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -418,8 +418,8 @@ impl McpTool for OpenNodesHandler {
|
||||
result.entities.insert(name, e);
|
||||
}
|
||||
}
|
||||
serde_json::to_string(&result).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_string(&result).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -620,7 +620,7 @@ impl McpTool for FindOrphansHandler {
|
||||
.collect::<Vec<String>>()
|
||||
});
|
||||
let data = serde_json::to_string(&orphans).unwrap_or_default();
|
||||
Ok(data.to_string())
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+100
-31
@@ -70,9 +70,9 @@ impl McpTool for QueryDecisionsHandler {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,9 +129,9 @@ impl McpTool for SearchErrorFixesHandler {
|
||||
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +180,8 @@ impl McpTool for QueryRecentChangesHandler {
|
||||
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())
|
||||
.read_with(|l| serde_json::to_string(l).map_err(|e| e.to_string()))?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ impl McpTool for ReadPreferencesHandler {
|
||||
let data = state
|
||||
.prefs
|
||||
.read_with(|prefs| serde_json::to_string(prefs).unwrap_or_default());
|
||||
Ok(data.to_string())
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,9 +319,9 @@ impl McpTool for ListTechDebtHandler {
|
||||
d.namespace == req.namespace && (req.include_resolved || !d.is_resolved)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,24 +339,39 @@ impl McpTool for OmniSearchHandler {
|
||||
|
||||
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.get_search_index();
|
||||
idx.search(&req.query, req.namespace.as_deref())
|
||||
.unwrap_or_default()
|
||||
let limit = req.limit.unwrap_or(5);
|
||||
let include_body = req.include_body.unwrap_or(false);
|
||||
let matches = match state.get_search_index().search(&req.query, req.namespace.as_deref()) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"Search query failed (possibly malformed Lucene syntax). Error: {}",
|
||||
e
|
||||
));
|
||||
}
|
||||
};
|
||||
println!("OMNI SEARCH MATCHES: {:?}", matches);
|
||||
|
||||
let kg_json = state.read_graph(|full| {
|
||||
let mut kg_entities = std::collections::HashMap::new();
|
||||
let mut count = 0;
|
||||
for (id, doc_type, _, _, _) in &matches {
|
||||
if doc_type == "entity"
|
||||
&& let Some(e) = full.entities.get(id)
|
||||
{
|
||||
kg_entities.insert(id, e);
|
||||
if count >= limit { continue; }
|
||||
count += 1;
|
||||
if !include_body {
|
||||
let mut summary = e.clone();
|
||||
summary.observations = vec![];
|
||||
kg_entities.insert(id.clone(), summary);
|
||||
} else {
|
||||
kg_entities.insert(id.clone(), e.clone());
|
||||
}
|
||||
}
|
||||
serde_json::to_value(&kg_entities).unwrap_or_default()
|
||||
});
|
||||
}
|
||||
serde_json::to_value(&kg_entities).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let mut matched_tasks = std::collections::HashSet::new();
|
||||
let mut matched_snippets = std::collections::HashSet::new();
|
||||
@@ -380,25 +395,58 @@ impl McpTool for OmniSearchHandler {
|
||||
let filtered: Vec<_> = all_tasks
|
||||
.iter()
|
||||
.filter(|t| matched_tasks.contains(t.id.as_str()))
|
||||
.take(limit)
|
||||
.map(|t| {
|
||||
if !include_body {
|
||||
let mut summary = t.clone();
|
||||
summary.description = "".to_string();
|
||||
summary.acceptance_criteria = vec![];
|
||||
summary
|
||||
} else {
|
||||
t.clone()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let snippets_json = state.snippets.read_with(|all_snippets| {
|
||||
let filtered: Vec<_> = all_snippets
|
||||
.iter()
|
||||
.filter(|s| matched_snippets.contains(s.name.as_str()))
|
||||
.take(limit)
|
||||
.map(|s| {
|
||||
if !include_body {
|
||||
let mut summary = s.clone();
|
||||
summary.code = "".to_string();
|
||||
summary
|
||||
} else {
|
||||
s.clone()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let adrs_json = state.adrs.read_with(|all_adrs| {
|
||||
let filtered: Vec<_> = all_adrs
|
||||
.iter()
|
||||
.filter(|a| matched_adrs.contains(a.id.as_str()))
|
||||
.take(limit)
|
||||
.map(|a| {
|
||||
if !include_body {
|
||||
let mut summary = a.clone();
|
||||
summary.context = "".to_string();
|
||||
summary.decision = "".to_string();
|
||||
summary.consequence = "".to_string();
|
||||
summary
|
||||
} else {
|
||||
a.clone()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let q = req.query;
|
||||
let tech_debts_json = state.tech_debts.read_with(|debts| {
|
||||
@@ -409,9 +457,20 @@ impl McpTool for OmniSearchHandler {
|
||||
&& (contains_ignore_ascii_case(&d.description, &q)
|
||||
|| contains_ignore_ascii_case(&d.ideal_solution, &q))
|
||||
})
|
||||
.take(limit)
|
||||
.map(|d| {
|
||||
if !include_body {
|
||||
let mut summary = d.clone();
|
||||
summary.description = "".to_string();
|
||||
summary.ideal_solution = "".to_string();
|
||||
summary
|
||||
} else {
|
||||
d.clone()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let memos_json = state.handoff_memos.read_with(|memos| {
|
||||
let filtered: Vec<_> = memos
|
||||
@@ -420,9 +479,19 @@ impl McpTool for OmniSearchHandler {
|
||||
req.namespace.as_ref().is_none_or(|ns| m.namespace == *ns)
|
||||
&& contains_ignore_ascii_case(&m.content, &q)
|
||||
})
|
||||
.take(limit)
|
||||
.map(|m| {
|
||||
if !include_body {
|
||||
let mut summary = m.clone();
|
||||
summary.content = "".to_string();
|
||||
summary
|
||||
} else {
|
||||
m.clone()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let error_fixes_json = state.error_fixes.read_with(|fixes| {
|
||||
let filtered: Vec<_> = fixes
|
||||
@@ -432,8 +501,8 @@ impl McpTool for OmniSearchHandler {
|
||||
|| contains_ignore_ascii_case(&f.solution, &q)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_value(&filtered).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_value(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
|
||||
let report = serde_json::json!({
|
||||
"knowledge_graph": kg_json,
|
||||
|
||||
@@ -49,8 +49,8 @@ impl McpTool for ReadStickyNotesHandler {
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> Result<String, String> {
|
||||
let data = state
|
||||
.sticky
|
||||
.read_with(|s| serde_json::to_string(s).unwrap_or_else(|_| "[]".to_string()));
|
||||
Ok(data.to_string())
|
||||
.read_with(|s| serde_json::to_string(s).map_err(|e| e.to_string()))?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,9 +167,9 @@ impl McpTool for ReadHandoffMemosHandler {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -293,8 +293,8 @@ impl McpTool for ListActiveTasksHandler {
|
||||
status_match && branch_match
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -474,8 +474,8 @@ impl McpTool for ListMilestonesHandler {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ impl McpTool for ListPinnedFilesHandler {
|
||||
ns_match && branch_match
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,9 +154,9 @@ impl McpTool for SearchSnippetsHandler {
|
||||
|| contains_ignore_ascii_case(&s.language, &query)
|
||||
})
|
||||
.collect();
|
||||
serde_json::to_string(&results).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&results).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,9 +245,9 @@ impl McpTool for LoadContextWorkspaceHandler {
|
||||
.iter()
|
||||
.filter(|w| w.namespace == req.namespace && w.name == req.name)
|
||||
.collect();
|
||||
serde_json::to_string(&filtered.first()).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered.first()).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,9 +271,9 @@ impl McpTool for ListContextWorkspacesHandler {
|
||||
serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let data = state.context_workspaces.read_with(|ws| {
|
||||
let filtered: Vec<_> = ws.iter().filter(|w| req.namespace.as_ref().map_or(true, |ns| &w.namespace == ns)).collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,9 +325,9 @@ impl McpTool for GetPrChecklistHandler {
|
||||
.iter()
|
||||
.filter(|i| i.namespace == req.namespace)
|
||||
.collect();
|
||||
serde_json::to_string(&filtered).unwrap_or_default()
|
||||
});
|
||||
Ok(data.to_string())
|
||||
serde_json::to_string(&filtered).map_err(|e| e.to_string())
|
||||
})?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-1
@@ -69,6 +69,10 @@ pub struct SearchNodesTool {
|
||||
pub query: String,
|
||||
/// Optional namespace to restrict the search to.
|
||||
pub namespace: Option<String>,
|
||||
/// Limit the number of results to avoid context limit overflow. Defaults to 10.
|
||||
pub limit: Option<usize>,
|
||||
/// Include the full observations of the matched items. If false, returns only IDs and types (recommended for LLMs to prevent context bloat). Defaults to false.
|
||||
pub include_body: Option<bool>,
|
||||
}
|
||||
|
||||
/// Open and retrieve full details of specific nodes in the knowledge graph.
|
||||
@@ -192,6 +196,10 @@ pub struct StoreSnippetTool {
|
||||
pub struct SearchSnippetsTool {
|
||||
/// The search query.
|
||||
pub query: String,
|
||||
/// Limit the number of results to avoid context limit overflow. Defaults to 5.
|
||||
pub limit: Option<usize>,
|
||||
/// Include the full code content of the matched items. If false, returns only IDs and descriptions (recommended for LLMs to prevent context bloat). Defaults to false.
|
||||
pub include_body: Option<bool>,
|
||||
}
|
||||
|
||||
/// Delete a stored code snippet.
|
||||
@@ -219,6 +227,10 @@ pub struct LogDecisionTool {
|
||||
pub struct QueryDecisionsTool {
|
||||
/// Optional search query.
|
||||
pub query: Option<String>,
|
||||
/// Limit the number of results to avoid context limit overflow. Defaults to 5.
|
||||
pub limit: Option<usize>,
|
||||
/// Include the full body/content of the matched ADRs. If false, returns only titles and IDs (recommended for LLMs to prevent context bloat). Defaults to false.
|
||||
pub include_body: Option<bool>,
|
||||
}
|
||||
|
||||
/// Merge two entities in the knowledge graph into one.
|
||||
@@ -265,6 +277,10 @@ pub struct LogErrorFixTool {
|
||||
pub struct SearchErrorFixesTool {
|
||||
/// The search query.
|
||||
pub query: String,
|
||||
/// Limit the number of results to avoid context limit overflow. Defaults to 5.
|
||||
pub limit: Option<usize>,
|
||||
/// Include the full solution details. If false, returns only error signatures (recommended for LLMs to prevent context bloat). Defaults to false.
|
||||
pub include_body: Option<bool>,
|
||||
}
|
||||
|
||||
/// Pin a file to keep it explicitly in the context workspace.
|
||||
@@ -523,12 +539,17 @@ pub struct ListContextWorkspacesTool {
|
||||
}
|
||||
|
||||
/// Search across all memory stores (Graph, Tasks, Snippets, ADRs, etc.).
|
||||
/// Highly recommended for discovery. Supports Lucene query syntax.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct OmniSearchTool {
|
||||
/// The search query.
|
||||
/// The search query. Supports Lucene syntax (e.g., 'title:"auth" AND status:open').
|
||||
pub query: String,
|
||||
/// Optional namespace to restrict the search to.
|
||||
pub namespace: Option<String>,
|
||||
/// Limit the number of results per category to avoid context limit overflow. Defaults to 5.
|
||||
pub limit: Option<usize>,
|
||||
/// Include the full body/content of the matched items. If false, returns only IDs and titles (recommended for LLMs to prevent context bloat). Defaults to false.
|
||||
pub include_body: Option<bool>,
|
||||
}
|
||||
|
||||
/// Get a health digest of the project.
|
||||
|
||||
Reference in new issue
Block a user