chore: fix clippy warnings and run rustfmt
This commit is contained in:
1 parent
fecd8bf33f
commit
3765e6b05d
21 files changed
+420
-369
No files matched your search
+45
-27
@@ -378,14 +378,17 @@ async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
|
||||
{
|
||||
let conn_lock = NVIM_CONN.lock().await;
|
||||
if let Some(sender) = conn_lock.as_ref()
|
||||
&& !sender.is_closed() {
|
||||
let active_lock = ACTIVE_SOCKET.lock().await;
|
||||
if let Some(ref current_socket) = *active_lock
|
||||
&& let Ok(target) = read_active_socket_file().await
|
||||
&& !target.is_empty() && &target == current_socket {
|
||||
return Ok(sender.clone());
|
||||
}
|
||||
&& !sender.is_closed()
|
||||
{
|
||||
let active_lock = ACTIVE_SOCKET.lock().await;
|
||||
if let Some(ref current_socket) = *active_lock
|
||||
&& let Ok(target) = read_active_socket_file().await
|
||||
&& !target.is_empty()
|
||||
&& &target == current_socket
|
||||
{
|
||||
return Ok(sender.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let interactive_socket = get_socket_path().await.ok();
|
||||
@@ -671,13 +674,14 @@ async fn call_nvim(req: rmpv::Value) -> Result<rmpv::Value, String> {
|
||||
};
|
||||
let (reply_tx, reply_rx) = oneshot::channel();
|
||||
|
||||
if let Err(_) = tx
|
||||
if tx
|
||||
.send(NvimRequest {
|
||||
msgid,
|
||||
req,
|
||||
reply: reply_tx,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
PENDING_REQUESTS.remove(&msgid);
|
||||
let mut conn = NVIM_CONN.lock().await;
|
||||
@@ -1341,20 +1345,30 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
|
||||
}
|
||||
"edit" => {
|
||||
let mut modified_args = args.clone();
|
||||
if let Some(edits) = modified_args.get_mut("edits").and_then(|v| v.as_array_mut()) {
|
||||
if let Some(edits) = modified_args
|
||||
.get_mut("edits")
|
||||
.and_then(|v| v.as_array_mut())
|
||||
{
|
||||
for edit in edits.iter_mut() {
|
||||
if let Some(content) = edit.get_mut("replacement_content") {
|
||||
if let Some(s) = content.as_str() {
|
||||
let lines: Vec<String> = s.split('\n').map(|l| l.trim_end_matches('\r').to_string()).collect();
|
||||
*content = serde_json::json!(lines);
|
||||
}
|
||||
if let Some(content) = edit.get_mut("replacement_content")
|
||||
&& let Some(s) = content.as_str()
|
||||
{
|
||||
let lines: Vec<String> = s
|
||||
.split('\n')
|
||||
.map(|l| l.trim_end_matches('\r').to_string())
|
||||
.collect();
|
||||
*content = serde_json::json!(lines);
|
||||
}
|
||||
}
|
||||
} else if let Some(content) = modified_args.get_mut("replacement_content") {
|
||||
if let Some(s) = content.as_str() {
|
||||
let lines: Vec<String> = s.split('\n').map(|l| l.trim_end_matches('\r').to_string()).collect();
|
||||
*content = serde_json::json!(lines);
|
||||
}
|
||||
} else if let Some(content) =
|
||||
modified_args.get_mut("replacement_content")
|
||||
&& let Some(s) = content.as_str()
|
||||
{
|
||||
let lines: Vec<String> = s
|
||||
.split('\n')
|
||||
.map(|l| l.trim_end_matches('\r').to_string())
|
||||
.collect();
|
||||
*content = serde_json::json!(lines);
|
||||
}
|
||||
let args_rmp = json_to_rmpv(&modified_args);
|
||||
let code = "
|
||||
@@ -1475,11 +1489,14 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
|
||||
}
|
||||
"create_scratch" | "open_buffer" => {
|
||||
let mut modified_args = args.clone();
|
||||
if let Some(content) = modified_args.get_mut("content") {
|
||||
if let Some(s) = content.as_str() {
|
||||
let lines: Vec<String> = s.split('\n').map(|l| l.trim_end_matches('\r').to_string()).collect();
|
||||
*content = serde_json::json!(lines);
|
||||
}
|
||||
if let Some(content) = modified_args.get_mut("content")
|
||||
&& let Some(s) = content.as_str()
|
||||
{
|
||||
let lines: Vec<String> = s
|
||||
.split('\n')
|
||||
.map(|l| l.trim_end_matches('\r').to_string())
|
||||
.collect();
|
||||
*content = serde_json::json!(lines);
|
||||
}
|
||||
let args_rmp = json_to_rmpv(&modified_args);
|
||||
let code = "
|
||||
@@ -1942,9 +1959,10 @@ pub async fn run_mcp_loop(app_name: &str, app_version: &str) {
|
||||
"highlight" | "clear_highlight" => {
|
||||
let mut modified_args = args.clone();
|
||||
if action == "clear_highlight"
|
||||
&& let Some(obj) = modified_args.as_object_mut() {
|
||||
obj.insert("clear_only".to_string(), json!(true));
|
||||
}
|
||||
&& let Some(obj) = modified_args.as_object_mut()
|
||||
{
|
||||
obj.insert("clear_only".to_string(), json!(true));
|
||||
}
|
||||
let args_rmp = json_to_rmpv(&modified_args);
|
||||
let code = "
|
||||
local args = ...
|
||||
|
||||
+12
-12
@@ -68,18 +68,18 @@ pub async fn handle_socket(socket: WebSocket, state: Arc<AppState>, _client_type
|
||||
.filter_map(|act_val| {
|
||||
if act_val["category"] == "TASK_EVENT"
|
||||
&& let Some(details_str) = act_val["details"].as_str()
|
||||
&& let Ok(event_val) =
|
||||
serde_json::from_str::<serde_json::Value>(details_str)
|
||||
{
|
||||
return Some(
|
||||
serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/task/completed",
|
||||
"params": event_val
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
&& let Ok(event_val) =
|
||||
serde_json::from_str::<serde_json::Value>(details_str)
|
||||
{
|
||||
return Some(
|
||||
serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/task/completed",
|
||||
"params": event_val
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
None
|
||||
})
|
||||
.take(5)
|
||||
|
||||
@@ -44,10 +44,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let path = entry.path();
|
||||
if path.extension().is_some_and(|ext| ext == "json")
|
||||
&& let Some(stem) = path.file_stem().and_then(|s| s.to_str())
|
||||
&& !active_tools.contains(stem) {
|
||||
println!(" [STALE REMOVED Win] {}", path.display());
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
&& !active_tools.contains(stem)
|
||||
{
|
||||
println!(" [STALE REMOVED Win] {}", path.display());
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +58,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let path = entry.path();
|
||||
if path.extension().is_some_and(|ext| ext == "json")
|
||||
&& let Some(stem) = path.file_stem().and_then(|s| s.to_str())
|
||||
&& !active_tools.contains(stem) {
|
||||
println!(" [STALE REMOVED WSL] {}", path.display());
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
&& !active_tools.contains(stem)
|
||||
{
|
||||
println!(" [STALE REMOVED WSL] {}", path.display());
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,13 @@ pub fn load_mcp_config_env() {
|
||||
{
|
||||
for (key, val) in env_obj {
|
||||
if let Some(val_str) = val.as_str()
|
||||
&& env::var(key).is_err() {
|
||||
unsafe {
|
||||
env::set_var(key, val_str);
|
||||
}
|
||||
info!("Loaded env var {} from mcp_config.json", key);
|
||||
&& env::var(key).is_err()
|
||||
{
|
||||
unsafe {
|
||||
env::set_var(key, val_str);
|
||||
}
|
||||
info!("Loaded env var {} from mcp_config.json", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -168,20 +168,20 @@ pub fn init_redb(base: &Path) -> Arc<Database> {
|
||||
} else {
|
||||
let migrated_path = json_path.with_extension("json.migrated");
|
||||
if std::fs::rename(&json_path, &migrated_path).is_err()
|
||||
&& migrated_path.exists() {
|
||||
let _ = std::fs::remove_file(&migrated_path);
|
||||
let _ = std::fs::rename(&json_path, &migrated_path);
|
||||
}
|
||||
&& migrated_path.exists()
|
||||
{
|
||||
let _ = std::fs::remove_file(&migrated_path);
|
||||
let _ = std::fs::rename(&json_path, &migrated_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if opened_ok
|
||||
&& let Err(e) = write_txn.commit() {
|
||||
tracing::error!("Failed to commit database migration transaction: {}", e);
|
||||
}
|
||||
if opened_ok && let Err(e) = write_txn.commit() {
|
||||
tracing::error!("Failed to commit database migration transaction: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
|
||||
@@ -259,13 +259,10 @@ pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
|
||||
let mut norm_a_sq = 0.0f32;
|
||||
let mut norm_b_sq = 0.0f32;
|
||||
|
||||
let chunks_a = a.chunks_exact(8);
|
||||
let chunks_b = b.chunks_exact(8);
|
||||
let (chunks_a, remainder_a) = a.as_chunks::<8>();
|
||||
let (chunks_b, remainder_b) = b.as_chunks::<8>();
|
||||
|
||||
let remainder_a = chunks_a.remainder();
|
||||
let remainder_b = chunks_b.remainder();
|
||||
|
||||
for (ca, cb) in chunks_a.zip(chunks_b) {
|
||||
for (ca, cb) in chunks_a.iter().zip(chunks_b.iter()) {
|
||||
for i in 0..8 {
|
||||
let x = ca[i];
|
||||
let y = cb[i];
|
||||
|
||||
@@ -120,9 +120,10 @@ impl McpTool for ReadFileSkeletonHandler {
|
||||
}
|
||||
}
|
||||
if header.is_empty()
|
||||
&& let Some(first_line) = node_text.lines().next() {
|
||||
header = first_line.to_string();
|
||||
}
|
||||
&& let Some(first_line) = node_text.lines().next()
|
||||
{
|
||||
header = first_line.to_string();
|
||||
}
|
||||
|
||||
out.push_str(&indent);
|
||||
out.push_str(header.trim());
|
||||
@@ -425,9 +426,10 @@ fn scan_workspace_for_symbol(
|
||||
break;
|
||||
}
|
||||
if let Ok(meta) = std::fs::metadata(path)
|
||||
&& meta.len() > 1024 * 1024 {
|
||||
continue;
|
||||
}
|
||||
&& meta.len() > 1024 * 1024
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Ok(content) = std::fs::read_to_string(path) {
|
||||
for (line_num, line) in content.lines().enumerate() {
|
||||
let is_match = if filter_fn_call {
|
||||
|
||||
+19
-18
@@ -140,27 +140,28 @@ impl McpTool for QueryGitDiffsHandler {
|
||||
let mut results = Vec::new();
|
||||
let cwd = env::current_dir().unwrap_or_default();
|
||||
if let Ok(repo) = git2::Repository::discover(&cwd)
|
||||
&& let Ok(mut revwalk) = repo.revwalk() {
|
||||
let _ = revwalk.push_head();
|
||||
let mut count = 0;
|
||||
for oid in revwalk.flatten() {
|
||||
if count >= remaining {
|
||||
break;
|
||||
}
|
||||
if let Ok(commit) = repo.find_commit(oid) {
|
||||
let summary = commit.summary().unwrap_or("");
|
||||
if summary.to_lowercase().contains(&q) {
|
||||
count += 1;
|
||||
results.push(json!({
|
||||
"commit_id": oid.to_string(),
|
||||
"author": commit.author().name().unwrap_or("unknown"),
|
||||
"message": summary,
|
||||
"timestamp": commit.time().seconds(),
|
||||
}));
|
||||
}
|
||||
&& let Ok(mut revwalk) = repo.revwalk()
|
||||
{
|
||||
let _ = revwalk.push_head();
|
||||
let mut count = 0;
|
||||
for oid in revwalk.flatten() {
|
||||
if count >= remaining {
|
||||
break;
|
||||
}
|
||||
if let Ok(commit) = repo.find_commit(oid) {
|
||||
let summary = commit.summary().unwrap_or("");
|
||||
if summary.to_lowercase().contains(&q) {
|
||||
count += 1;
|
||||
results.push(json!({
|
||||
"commit_id": oid.to_string(),
|
||||
"author": commit.author().name().unwrap_or("unknown"),
|
||||
"message": summary,
|
||||
"timestamp": commit.time().seconds(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
results
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -63,16 +63,17 @@ impl<'a> GraphQueryBuilder<'a> {
|
||||
}
|
||||
nodes_at_current_depth -= 1;
|
||||
if current_depth < self.max_depth
|
||||
&& let Some(neighbors) = adj.get(current) {
|
||||
for &(neighbor, rel_type, is_inverse) in neighbors {
|
||||
if !visited.contains(neighbor) {
|
||||
visited.insert(neighbor);
|
||||
parents.insert(neighbor, (current, rel_type, is_inverse));
|
||||
queue.push_back(neighbor);
|
||||
nodes_at_next_depth += 1;
|
||||
}
|
||||
&& let Some(neighbors) = adj.get(current)
|
||||
{
|
||||
for &(neighbor, rel_type, is_inverse) in neighbors {
|
||||
if !visited.contains(neighbor) {
|
||||
visited.insert(neighbor);
|
||||
parents.insert(neighbor, (current, rel_type, is_inverse));
|
||||
queue.push_back(neighbor);
|
||||
nodes_at_next_depth += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if nodes_at_current_depth == 0 {
|
||||
current_depth += 1;
|
||||
nodes_at_current_depth = nodes_at_next_depth;
|
||||
@@ -997,13 +998,14 @@ impl McpTool for GetSubgraphHandler {
|
||||
|
||||
while let Some((curr, d)) = queue.pop_front() {
|
||||
if d < depth
|
||||
&& let Some(neighbors) = adj.get(curr) {
|
||||
for &next in neighbors {
|
||||
if visited.insert(next.to_string()) {
|
||||
queue.push_back((next, d + 1));
|
||||
}
|
||||
&& let Some(neighbors) = adj.get(curr)
|
||||
{
|
||||
for &next in neighbors {
|
||||
if visited.insert(next.to_string()) {
|
||||
queue.push_back((next, d + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ent: std::collections::HashMap<String, crate::models::Entity> = visited
|
||||
@@ -1176,9 +1178,11 @@ impl McpTool for ResolveStaleSymbolsHandler {
|
||||
state.modify_graph(|g| {
|
||||
for name in g.entities.keys() {
|
||||
if let Some(target) = &req.target
|
||||
&& name != target && !name.contains(target) {
|
||||
continue;
|
||||
}
|
||||
&& name != target
|
||||
&& !name.contains(target)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
checked += 1;
|
||||
let path = std::path::Path::new(name);
|
||||
if (name.contains('/') || name.contains('\\') || name.contains('.'))
|
||||
|
||||
+68
-54
@@ -111,9 +111,10 @@ impl McpTool for SearchErrorFixesHandler {
|
||||
score += 0.5;
|
||||
}
|
||||
if let Some(emb) = &f.embedding
|
||||
&& !query_emb.is_empty() {
|
||||
score += crate::embedding::cosine_similarity(&query_emb, emb);
|
||||
}
|
||||
&& !query_emb.is_empty()
|
||||
{
|
||||
score += crate::embedding::cosine_similarity(&query_emb, emb);
|
||||
}
|
||||
(f, score)
|
||||
})
|
||||
.filter(|(_, score)| *score > 0.1)
|
||||
@@ -198,9 +199,10 @@ impl McpTool for LogCodeChangeHandler {
|
||||
description = format!("{} [Line Range: {}]", description, range);
|
||||
}
|
||||
if let Some(symbols) = &req.symbol_references
|
||||
&& !symbols.is_empty() {
|
||||
description = format!("{} [Symbols: {}]", description, symbols.join(", "));
|
||||
}
|
||||
&& !symbols.is_empty()
|
||||
{
|
||||
description = format!("{} [Symbols: {}]", description, symbols.join(", "));
|
||||
}
|
||||
|
||||
if state.ollama.is_available().await {
|
||||
let prompt = format!(
|
||||
@@ -244,13 +246,12 @@ impl McpTool for LogCodeChangeHandler {
|
||||
let detected_vcs = if let Some(vcs) = req.vcs_type.clone() {
|
||||
Some(vcs)
|
||||
} else if let Some(ref rev) = effective_rev {
|
||||
if rev.starts_with('r') && rev[1..].chars().all(|c| c.is_ascii_digit()) {
|
||||
Some("svn".to_string())
|
||||
} else if req
|
||||
.repo_url
|
||||
.as_deref()
|
||||
.map(|u| u.contains("/svn/"))
|
||||
.unwrap_or(false)
|
||||
if (rev.starts_with('r') && rev[1..].chars().all(|c| c.is_ascii_digit()))
|
||||
|| req
|
||||
.repo_url
|
||||
.as_deref()
|
||||
.map(|u| u.contains("/svn/"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some("svn".to_string())
|
||||
} else {
|
||||
@@ -337,21 +338,23 @@ impl McpTool for QueryRecentChangesHandler {
|
||||
.rev()
|
||||
.filter(|c| {
|
||||
if let Some(ns) = &req.namespace
|
||||
&& !c.namespace.eq_ignore_ascii_case(ns) {
|
||||
return false;
|
||||
}
|
||||
&& !c.namespace.eq_ignore_ascii_case(ns)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(repo) = &req.repo_name
|
||||
&& c.repo_name
|
||||
.as_ref()
|
||||
.map(|rn| !rn.eq_ignore_ascii_case(repo))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(vcs) = &req.vcs_type
|
||||
&& !c.effective_vcs().eq_ignore_ascii_case(vcs) {
|
||||
return false;
|
||||
}
|
||||
&& !c.effective_vcs().eq_ignore_ascii_case(vcs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
})
|
||||
.skip(offset)
|
||||
@@ -464,10 +467,11 @@ impl McpTool for DecisionsHandler {
|
||||
&& let Some(s_pos) = adrs
|
||||
.iter()
|
||||
.position(|a| a.id.eq_ignore_ascii_case(superseded_id))
|
||||
&& s_pos != pos {
|
||||
adrs[s_pos].status = "superseded".to_string();
|
||||
adrs_to_index.push(adrs[s_pos].clone());
|
||||
}
|
||||
&& s_pos != pos
|
||||
{
|
||||
adrs[s_pos].status = "superseded".to_string();
|
||||
adrs_to_index.push(adrs[s_pos].clone());
|
||||
}
|
||||
|
||||
let a = &mut adrs[pos];
|
||||
if let Some(t) = req.title {
|
||||
@@ -1273,20 +1277,23 @@ impl McpTool for ManageCheckpointHandler {
|
||||
}
|
||||
|
||||
if let Ok(graph_content) = std::fs::read_to_string(target_dir.join("graph.json"))
|
||||
&& let Ok(graph) = serde_json::from_str(&graph_content) {
|
||||
state.graph.modify(|g| *g = graph);
|
||||
}
|
||||
&& let Ok(graph) = serde_json::from_str(&graph_content)
|
||||
{
|
||||
state.graph.modify(|g| *g = graph);
|
||||
}
|
||||
|
||||
if let Ok(tasks_content) = std::fs::read_to_string(target_dir.join("tasks.json"))
|
||||
&& let Ok(tasks) = serde_json::from_str(&tasks_content) {
|
||||
state.project.tasks.modify(|t| *t = tasks);
|
||||
}
|
||||
&& let Ok(tasks) = serde_json::from_str(&tasks_content)
|
||||
{
|
||||
state.project.tasks.modify(|t| *t = tasks);
|
||||
}
|
||||
|
||||
if let Ok(debts_content) =
|
||||
std::fs::read_to_string(target_dir.join("tech_debts.json"))
|
||||
&& let Ok(debts) = serde_json::from_str(&debts_content) {
|
||||
state.code.tech_debts.modify(|d| *d = debts);
|
||||
}
|
||||
&& let Ok(debts) = serde_json::from_str(&debts_content)
|
||||
{
|
||||
state.code.tech_debts.modify(|d| *d = debts);
|
||||
}
|
||||
|
||||
Ok(format!("Checkpoint '{}' restored successfully.", name))
|
||||
}
|
||||
@@ -1296,9 +1303,10 @@ impl McpTool for ManageCheckpointHandler {
|
||||
if let Ok(entries) = std::fs::read_dir(&checkpoints_dir) {
|
||||
for entry in entries.flatten() {
|
||||
if entry.path().is_dir()
|
||||
&& let Some(n) = entry.file_name().to_str() {
|
||||
list.push(serde_json::json!({"type": "checkpoint", "name": n}));
|
||||
}
|
||||
&& let Some(n) = entry.file_name().to_str()
|
||||
{
|
||||
list.push(serde_json::json!({"type": "checkpoint", "name": n}));
|
||||
}
|
||||
}
|
||||
}
|
||||
let snaps = state.project.snapshots.read_with(|snaps| snaps.clone());
|
||||
@@ -1467,9 +1475,10 @@ impl McpTool for GetNextActionableTasksHandler {
|
||||
|
||||
if let Some(branch) = &req.git_branch
|
||||
&& let Some(tb) = &task.git_branch
|
||||
&& tb != branch {
|
||||
continue;
|
||||
}
|
||||
&& tb != branch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let unblocked = task.dependencies.is_empty()
|
||||
|| task.dependencies.iter().all(|d| completed_ids.contains(d));
|
||||
@@ -1541,9 +1550,10 @@ impl McpTool for HypothesesHandler {
|
||||
.into_iter()
|
||||
.filter(|h| {
|
||||
if let Some(tid) = &req.task_id
|
||||
&& h.task_id.as_ref() != Some(tid) {
|
||||
return false;
|
||||
}
|
||||
&& h.task_id.as_ref() != Some(tid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(q) = &req.query {
|
||||
let lq = q.to_lowercase();
|
||||
return h.hypothesis.to_lowercase().contains(&lq)
|
||||
@@ -1725,20 +1735,24 @@ impl McpTool for AgentSignalsHandler {
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
if let Some(ttl) = s.ttl_seconds
|
||||
&& now > s.timestamp + ttl {
|
||||
return false;
|
||||
}
|
||||
&& now > s.timestamp + ttl
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(sender) = &req.sender
|
||||
&& s.sender.to_lowercase() != sender.to_lowercase() {
|
||||
return false;
|
||||
}
|
||||
&& s.sender.to_lowercase() != sender.to_lowercase()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(st) = &req.signal_type
|
||||
&& s.signal_type.to_lowercase() != st.to_lowercase() {
|
||||
return false;
|
||||
}
|
||||
&& s.signal_type.to_lowercase() != st.to_lowercase()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
})
|
||||
.take(req.limit.unwrap_or(20)).cloned()
|
||||
.take(req.limit.unwrap_or(20))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
|
||||
@@ -245,11 +245,11 @@ pub fn recalculate_milestones(state: &MemoryState) -> Vec<String> {
|
||||
&& (t.status == "in_progress" || t.status == "active")
|
||||
}))
|
||||
&& !m.status.eq_ignore_ascii_case("in_progress")
|
||||
&& !m.status.eq_ignore_ascii_case("completed")
|
||||
{
|
||||
m.status = "in_progress".to_string();
|
||||
updated.push(m.id.clone());
|
||||
}
|
||||
&& !m.status.eq_ignore_ascii_case("completed")
|
||||
{
|
||||
m.status = "in_progress".to_string();
|
||||
updated.push(m.id.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -287,9 +287,10 @@ pub async fn reconcile_commit_or_code_change(
|
||||
return true;
|
||||
}
|
||||
if let (Some(n1), Some(n2)) = (adr_numeric_id(&adr.id), adr_numeric_id(cand))
|
||||
&& n1 == n2 {
|
||||
return true;
|
||||
}
|
||||
&& n1 == n2
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ impl McpTool for TasksHandler {
|
||||
priority: req.priority,
|
||||
assigned_agent: req.assigned_agent,
|
||||
verification_command: req.verification_command,
|
||||
..Default::default()
|
||||
};
|
||||
let idx = state.get_search_index().await;
|
||||
drop(idx.index_task(&task));
|
||||
@@ -479,9 +478,10 @@ impl McpTool for MilestonesHandler {
|
||||
m.status = status.clone();
|
||||
if (status.eq_ignore_ascii_case("completed")
|
||||
|| status.eq_ignore_ascii_case("done"))
|
||||
&& m.completed_at.is_none() {
|
||||
m.completed_at = Some(crate::handlers::utils::now_secs());
|
||||
}
|
||||
&& m.completed_at.is_none()
|
||||
{
|
||||
m.completed_at = Some(crate::handlers::utils::now_secs());
|
||||
}
|
||||
if let Some(task_ids) = &req.task_ids {
|
||||
m.task_ids = task_ids.clone();
|
||||
if let Some(deliverables) = &req.deliverables {
|
||||
|
||||
@@ -191,10 +191,9 @@ pub fn html_to_markdown(html: &str) -> String {
|
||||
out.push_str("* ");
|
||||
}
|
||||
}
|
||||
"/li"
|
||||
if !in_cell => {
|
||||
out.push('\n');
|
||||
}
|
||||
"/li" if !in_cell => {
|
||||
out.push('\n');
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if c == '&' {
|
||||
@@ -510,7 +509,8 @@ pub async fn process_clipboard_snapshot(state: Arc<MemoryState>) {
|
||||
.search_index
|
||||
.read()
|
||||
.await
|
||||
.index_clipboard_image(&doc_id, &path_str, text);
|
||||
.index_clipboard_image(&doc_id, &path_str, text)
|
||||
.await;
|
||||
}
|
||||
|
||||
let cached = crate::state::CachedClipboardImage {
|
||||
@@ -567,9 +567,10 @@ pub async fn process_clipboard_snapshot(state: Arc<MemoryState>) {
|
||||
|
||||
let mut cache = state.clipboard_cache.write().await;
|
||||
if let Some(ref prev) = cache.last_text
|
||||
&& prev.text == cached.text {
|
||||
return;
|
||||
}
|
||||
&& prev.text == cached.text
|
||||
{
|
||||
return;
|
||||
}
|
||||
cache.last_text = Some(cached.clone());
|
||||
cache
|
||||
.history
|
||||
@@ -891,12 +892,10 @@ impl McpTool for ClipboardHandler {
|
||||
}
|
||||
|
||||
if let Some(eng) = engine
|
||||
&& let Some(ocr_text) = perform_ocrs_ocr(eng, &dynamic_img) {
|
||||
out.insert(
|
||||
"image_analysis".to_string(),
|
||||
json!(ocr_text.trim()),
|
||||
);
|
||||
}
|
||||
&& let Some(ocr_text) = perform_ocrs_ocr(eng, &dynamic_img)
|
||||
{
|
||||
out.insert("image_analysis".to_string(), json!(ocr_text.trim()));
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
},
|
||||
@@ -905,9 +904,7 @@ impl McpTool for ClipboardHandler {
|
||||
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
|
||||
|
||||
state.record_activity("clipboard", "Read contents from OS clipboard", None);
|
||||
Ok::<String, crate::error::AppError>(serde_yaml::to_string(&Value::Object(
|
||||
out,
|
||||
))?)
|
||||
Ok::<String, crate::error::AppError>(serde_yaml::to_string(&Value::Object(out))?)
|
||||
}
|
||||
ClipboardAction::Write => {
|
||||
let text_opt = req.text;
|
||||
|
||||
@@ -163,7 +163,7 @@ fn extract_chunks_with_parent(
|
||||
node: Node,
|
||||
code: &str,
|
||||
chunks: &mut Vec<(String, String, String)>,
|
||||
ext: &str,
|
||||
_ext: &str,
|
||||
parent_scope: Option<&str>,
|
||||
depth: usize,
|
||||
) {
|
||||
@@ -229,7 +229,7 @@ fn extract_chunks_with_parent(
|
||||
} else {
|
||||
let mut cursor = node.walk();
|
||||
for child in node.named_children(&mut cursor) {
|
||||
extract_chunks_with_parent(child, code, chunks, ext, current_scope, depth + 1);
|
||||
extract_chunks_with_parent(child, code, chunks, _ext, current_scope, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+62
-58
@@ -107,9 +107,10 @@ pub async fn ttl_sweeper_worker(state: Arc<MemoryState>) {
|
||||
state.project.tasks.read_with(|tasks| {
|
||||
for t in tasks.iter() {
|
||||
if let Some(exp) = t.expires_at
|
||||
&& t.is_active() {
|
||||
next_expiry = Some(next_expiry.map_or(exp, |curr| curr.min(exp)));
|
||||
}
|
||||
&& t.is_active()
|
||||
{
|
||||
next_expiry = Some(next_expiry.map_or(exp, |curr| curr.min(exp)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -140,7 +141,7 @@ pub async fn ttl_sweeper_worker(state: Arc<MemoryState>) {
|
||||
let sleep_duration = match next_expiry {
|
||||
Some(exp) if exp > now => {
|
||||
let diff = exp - now;
|
||||
std::time::Duration::from_secs(diff.min(60).max(1))
|
||||
std::time::Duration::from_secs(diff.clamp(1, 60))
|
||||
}
|
||||
Some(_) => std::time::Duration::from_millis(50),
|
||||
None => std::time::Duration::from_secs(60),
|
||||
@@ -161,11 +162,13 @@ pub async fn ttl_sweeper_worker(state: Arc<MemoryState>) {
|
||||
state.project.tasks.modify(|tasks| {
|
||||
for t in tasks.iter_mut() {
|
||||
if let Some(exp) = t.expires_at
|
||||
&& exp <= now && t.is_active() {
|
||||
t.status = "expired".to_string();
|
||||
t.updated_at = now;
|
||||
expired_tasks.push(t.id.clone());
|
||||
}
|
||||
&& exp <= now
|
||||
&& t.is_active()
|
||||
{
|
||||
t.status = "expired".to_string();
|
||||
t.updated_at = now;
|
||||
expired_tasks.push(t.id.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -249,27 +252,28 @@ pub async fn condense_graph_worker(state: Arc<MemoryState>) {
|
||||
});
|
||||
|
||||
if let Some((content, names)) = snippet_condensation
|
||||
&& !content.is_empty() {
|
||||
let name = format!("Snippet History {}", now);
|
||||
state.modify_graph(|graph| {
|
||||
graph.entities.insert(
|
||||
name.clone(),
|
||||
crate::models::Entity {
|
||||
name: name.clone(),
|
||||
entity_type: "Historical Summary".to_string(),
|
||||
observations: vec![content],
|
||||
namespace: crate::models::default_namespace(),
|
||||
git_branch: None,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
});
|
||||
let name_set: std::collections::HashSet<String> = names.into_iter().collect();
|
||||
state.code.snippets.modify(|snippets| {
|
||||
snippets.retain(|s| !name_set.contains(&s.name));
|
||||
});
|
||||
tracing::info!("Condensed snippets into Historical Summary.");
|
||||
}
|
||||
&& !content.is_empty()
|
||||
{
|
||||
let name = format!("Snippet History {}", now);
|
||||
state.modify_graph(|graph| {
|
||||
graph.entities.insert(
|
||||
name.clone(),
|
||||
crate::models::Entity {
|
||||
name: name.clone(),
|
||||
entity_type: "Historical Summary".to_string(),
|
||||
observations: vec![content],
|
||||
namespace: crate::models::default_namespace(),
|
||||
git_branch: None,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
});
|
||||
let name_set: std::collections::HashSet<String> = names.into_iter().collect();
|
||||
state.code.snippets.modify(|snippets| {
|
||||
snippets.retain(|s| !name_set.contains(&s.name));
|
||||
});
|
||||
tracing::info!("Condensed snippets into Historical Summary.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,36 +323,37 @@ pub async fn memory_consolidation_worker(state: Arc<MemoryState>) {
|
||||
.trim_end_matches("```")
|
||||
.trim();
|
||||
if let Ok(duplicates) = serde_json::from_str::<Vec<String>>(cleaned)
|
||||
&& duplicates.len() == 2 {
|
||||
let e1_name = &duplicates[0];
|
||||
let e2_name = &duplicates[1];
|
||||
&& duplicates.len() == 2
|
||||
{
|
||||
let e1_name = &duplicates[0];
|
||||
let e2_name = &duplicates[1];
|
||||
|
||||
if e1_name != e2_name {
|
||||
tracing::info!(
|
||||
"Memory Consolidation Daemon: Merging '{}' into '{}'",
|
||||
e2_name,
|
||||
e1_name
|
||||
);
|
||||
state.modify_graph(|g| {
|
||||
if let Some(mut e2) = g.entities.remove(e2_name) {
|
||||
if let Some(e1) = g.entities.get_mut(e1_name) {
|
||||
e1.observations.append(&mut e2.observations);
|
||||
} else {
|
||||
g.entities.insert(e2_name.clone(), e2);
|
||||
}
|
||||
if e1_name != e2_name {
|
||||
tracing::info!(
|
||||
"Memory Consolidation Daemon: Merging '{}' into '{}'",
|
||||
e2_name,
|
||||
e1_name
|
||||
);
|
||||
state.modify_graph(|g| {
|
||||
if let Some(mut e2) = g.entities.remove(e2_name) {
|
||||
if let Some(e1) = g.entities.get_mut(e1_name) {
|
||||
e1.observations.append(&mut e2.observations);
|
||||
} else {
|
||||
g.entities.insert(e2_name.clone(), e2);
|
||||
}
|
||||
}
|
||||
|
||||
for rel in g.relations.iter_mut() {
|
||||
if rel.from == *e2_name {
|
||||
rel.from = e1_name.clone();
|
||||
}
|
||||
if rel.to == *e2_name {
|
||||
rel.to = e1_name.clone();
|
||||
}
|
||||
for rel in g.relations.iter_mut() {
|
||||
if rel.from == *e2_name {
|
||||
rel.from = e1_name.clone();
|
||||
}
|
||||
});
|
||||
}
|
||||
if rel.to == *e2_name {
|
||||
rel.to = e1_name.clone();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,13 +471,12 @@ pub async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::erro
|
||||
event.payload.get("action").and_then(|a| a.as_str()),
|
||||
event.payload.get("target").and_then(|t| t.as_str()),
|
||||
event.payload.get("status").and_then(|s| s.as_str()),
|
||||
) {
|
||||
if (status == "authorized" || status == "blocked")
|
||||
)
|
||||
&& (status == "authorized" || status == "blocked")
|
||||
&& let Some(addr) = subscribers.remove(&(action.to_string(), target.to_string())) {
|
||||
let response = if status == "authorized" { b"APPROVED" } else { b"REJECTED" };
|
||||
let _ = socket.send_to(response, addr).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -13,7 +13,6 @@ pub enum ChangeKind {
|
||||
Renamed,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct CodeChange {
|
||||
pub timestamp: u64,
|
||||
@@ -55,16 +54,18 @@ impl CodeChange {
|
||||
return vcs.as_str();
|
||||
}
|
||||
if let Some(rev) = self.revision.as_deref().or(self.git_commit.as_deref())
|
||||
&& rev.starts_with('r') && rev[1..].chars().all(|c| c.is_ascii_digit()) {
|
||||
return "svn";
|
||||
}
|
||||
&& rev.starts_with('r')
|
||||
&& rev[1..].chars().all(|c| c.is_ascii_digit())
|
||||
{
|
||||
return "svn";
|
||||
}
|
||||
if let Some(br) = self.branch.as_deref().or(self.git_branch.as_deref())
|
||||
&& (br.eq_ignore_ascii_case("trunk")
|
||||
|| br.starts_with("branches/")
|
||||
|| br.starts_with("tags/"))
|
||||
{
|
||||
return "svn";
|
||||
}
|
||||
{
|
||||
return "svn";
|
||||
}
|
||||
"git"
|
||||
}
|
||||
|
||||
@@ -262,9 +263,10 @@ impl KnowledgeGraph {
|
||||
HashMap::with_capacity(self.relations.len() * 2);
|
||||
for rel in &self.relations {
|
||||
if let Some(rf) = relation_filter
|
||||
&& rel.relation_type != rf {
|
||||
continue;
|
||||
}
|
||||
&& rel.relation_type != rf
|
||||
{
|
||||
continue;
|
||||
}
|
||||
adj.entry(rel.from.as_str()).or_default().push((
|
||||
rel.to.as_str(),
|
||||
rel.relation_type.as_str(),
|
||||
|
||||
@@ -552,9 +552,10 @@ impl SearchService {
|
||||
self.state.read_graph(|graph| {
|
||||
for entity in graph.entities.values() {
|
||||
if let Some(ns) = filter_namespace
|
||||
&& entity.namespace != ns {
|
||||
continue;
|
||||
}
|
||||
&& entity.namespace != ns
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let obs = entity.observations.join("; ");
|
||||
let desc = format!("{}: {}", entity.entity_type, obs);
|
||||
if let Some(ref emb) = entity.embedding {
|
||||
|
||||
+7
-7
@@ -87,9 +87,10 @@ impl ClipboardCacheState {
|
||||
|
||||
pub fn push_text(&mut self, text: CachedClipboardText) {
|
||||
if let Some(ref prev) = self.last_text
|
||||
&& prev.text == text.text {
|
||||
return;
|
||||
}
|
||||
&& prev.text == text.text
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.last_text = Some(text.clone());
|
||||
self.history.push_front(ClipboardHistoryItem::Text(text));
|
||||
if self.history.len() > 20 {
|
||||
@@ -141,10 +142,9 @@ impl MemoryState {
|
||||
pub fn new(base_dir_str: &str) -> Self {
|
||||
let is_in_memory = base_dir_str == ":memory:";
|
||||
let base = std::path::PathBuf::from(base_dir_str);
|
||||
if !is_in_memory
|
||||
&& let Err(e) = std::fs::create_dir_all(&base) {
|
||||
tracing::error!("Failed to create store directory at {:?}: {}", base, e);
|
||||
}
|
||||
if !is_in_memory && let Err(e) = std::fs::create_dir_all(&base) {
|
||||
tracing::error!("Failed to create store directory at {:?}: {}", base, e);
|
||||
}
|
||||
|
||||
let db = crate::db::init_redb(&base);
|
||||
|
||||
|
||||
+6
-3
@@ -34,9 +34,11 @@ static QUEUE_REGISTRY: std::sync::Mutex<Option<(Arc<Database>, DbWriteQueue)>> =
|
||||
fn get_or_create_queue(db: Arc<Database>) -> DbWriteQueue {
|
||||
let mut reg = QUEUE_REGISTRY.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if let Some((ref existing_db, ref queue)) = *reg
|
||||
&& Arc::ptr_eq(existing_db, &db) && !queue.tx.is_closed() {
|
||||
return queue.clone();
|
||||
}
|
||||
&& Arc::ptr_eq(existing_db, &db)
|
||||
&& !queue.tx.is_closed()
|
||||
{
|
||||
return queue.clone();
|
||||
}
|
||||
let new_queue = DbWriteQueue::new(db.clone());
|
||||
*reg = Some((db, new_queue.clone()));
|
||||
new_queue
|
||||
@@ -458,6 +460,7 @@ impl<T: DeserializeOwned + Default + Serialize + Send + Sync + 'static> Store<T>
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn prepare_batch(
|
||||
&self,
|
||||
new_snapshot: &T,
|
||||
|
||||
@@ -612,6 +612,7 @@ pub enum HandoffMemoAction {
|
||||
/// - 'leave': Post a handoff memo. Required: content. Optional: namespace.
|
||||
/// - 'read': Inspect active memos. Optional: namespace.
|
||||
/// - 'clear': Clear memos. Optional: namespace, ids.
|
||||
///
|
||||
/// Next steps on error: Check parameter requirements for 'leave' or 'clear' actions.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct HandoffMemosTool {
|
||||
@@ -655,6 +656,7 @@ pub enum TaskAction {
|
||||
/// - 'list': List active tasks. Optional: git_branch, summary_level ('compact'|'detailed'|'full'), max_tokens.
|
||||
/// - 'set_criteria': Set acceptance criteria. Required: id, criteria (array of strings).
|
||||
/// - 'verify': Verify criteria met. Required: id.
|
||||
///
|
||||
/// Next steps on error: Check required parameters or call list to verify task IDs.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct TasksTool {
|
||||
@@ -719,6 +721,7 @@ pub enum MilestoneAction {
|
||||
/// - 'add': Create milestone. Required: title. Optional: namespace.
|
||||
/// - 'update': Update milestone status. Required: id, status.
|
||||
/// - 'list': List milestones. Optional: namespace.
|
||||
///
|
||||
/// Next steps on error: Provide required title for 'add' or id/status for 'update'.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct MilestonesTool {
|
||||
@@ -765,6 +768,7 @@ pub enum SnippetAction {
|
||||
/// - 'search': Search snippets. Optional: query, tags, hybrid (boolean for BM25+vector search).
|
||||
/// - 'delete': Delete snippet. Required: id (or query) as snippet name.
|
||||
/// - 'tag': Tag snippet. Required: id (or query), tags (array of strings).
|
||||
///
|
||||
/// Next steps on error: Ensure snippet name/query or id is provided.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct SnippetsTool {
|
||||
@@ -815,6 +819,7 @@ pub enum DecisionAction {
|
||||
/// - 'query': Search ADRs. Optional: query, namespace.
|
||||
/// - 'update': Update ADR status or implementation info. Required: id. Optional: status, git_commit, git_branch, task_id, context, decision, consequences, title.
|
||||
/// - 'delete': Remove ADR. Required: id.
|
||||
///
|
||||
/// Next steps on error: Provide title for 'log' or id for 'update'/'delete'.
|
||||
#[derive(Deserialize, Serialize, JsonSchema, Debug, Clone)]
|
||||
pub struct DecisionsTool {
|
||||
@@ -875,6 +880,7 @@ pub enum TechDebtAction {
|
||||
/// - 'log': Record technical debt. Required: description. Optional: ideal_solution, git_commit, git_branch, symbol_references, line_range, namespace.
|
||||
/// - 'resolve': Mark tech debt resolved. Required: id.
|
||||
/// - 'list': List tech debt items. Optional: include_resolved, namespace.
|
||||
///
|
||||
/// Next steps on error: Provide description for 'log' or valid ID for 'resolve'.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct TechDebtTool {
|
||||
@@ -963,6 +969,7 @@ pub enum EnvAction {
|
||||
/// - 'log_requirement': Log environment variable requirement. Required: key. Optional: description, is_secret, namespace.
|
||||
/// - 'register': Register remote target environment. Required: name. Optional: url, description, requires_vpn, namespace.
|
||||
/// - 'get_details': Get full environment details. Optional: namespace.
|
||||
///
|
||||
/// Next steps on error: Provide key for 'log_requirement' or name for 'register'.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct EnvironmentTool {
|
||||
|
||||
+94
-97
@@ -105,109 +105,106 @@ async fn trigger_autonomous_review(path: &Path, state: Arc<MemoryState>) {
|
||||
// ADR-0109: Incremental Background AST Indexing & Differential Graph Updates
|
||||
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
|
||||
if matches!(ext, "rs" | "ts" | "js" | "py" | "go" | "java" | "c" | "cpp")
|
||||
&& let Ok(content) = std::fs::read_to_string(path) {
|
||||
let language = match ext {
|
||||
"rs" => Some(tree_sitter_rust::LANGUAGE),
|
||||
"ts" | "js" => Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT),
|
||||
"py" => Some(tree_sitter_python::LANGUAGE),
|
||||
"java" => Some(tree_sitter_java::LANGUAGE),
|
||||
"c" => Some(tree_sitter_c::LANGUAGE),
|
||||
"cpp" => Some(tree_sitter_cpp::LANGUAGE),
|
||||
"go" => Some(tree_sitter_go::LANGUAGE),
|
||||
_ => None,
|
||||
};
|
||||
&& let Ok(content) = std::fs::read_to_string(path)
|
||||
{
|
||||
let language = match ext {
|
||||
"rs" => Some(tree_sitter_rust::LANGUAGE),
|
||||
"ts" | "js" => Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT),
|
||||
"py" => Some(tree_sitter_python::LANGUAGE),
|
||||
"java" => Some(tree_sitter_java::LANGUAGE),
|
||||
"c" => Some(tree_sitter_c::LANGUAGE),
|
||||
"cpp" => Some(tree_sitter_cpp::LANGUAGE),
|
||||
"go" => Some(tree_sitter_go::LANGUAGE),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(lang) = language {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
if parser.set_language(&lang.into()).is_ok()
|
||||
&& let Some(tree) = parser.parse(&content, None) {
|
||||
let mut chunks = Vec::new();
|
||||
crate::indexer::extract_chunks(
|
||||
tree.root_node(),
|
||||
&content,
|
||||
&mut chunks,
|
||||
ext,
|
||||
if let Some(lang) = language {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
if parser.set_language(&lang.into()).is_ok()
|
||||
&& let Some(tree) = parser.parse(&content, None)
|
||||
{
|
||||
let mut chunks = Vec::new();
|
||||
crate::indexer::extract_chunks(tree.root_node(), &content, &mut chunks, ext);
|
||||
let file_str = path.to_string_lossy().to_string();
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
|
||||
let chunks_count = chunks.len();
|
||||
let file_str_clone = file_str.clone();
|
||||
state.modify_graph(|g| {
|
||||
// Ensure File entity exists
|
||||
g.entities
|
||||
.entry(file_str.clone())
|
||||
.or_insert_with(|| crate::models::Entity {
|
||||
name: file_str.clone(),
|
||||
entity_type: "File".to_string(),
|
||||
namespace: "global".to_string(),
|
||||
file_path: Some(file_str.clone()),
|
||||
created_at: Some(now),
|
||||
updated_at: Some(now),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
for (chunk_name, chunk_code, chunk_desc) in chunks {
|
||||
let symbol_name = format!("{}::{}", file_str, chunk_name);
|
||||
let symbol_type = if chunk_desc.contains("struct") {
|
||||
"DataStructure".to_string()
|
||||
} else {
|
||||
"McpTool".to_string()
|
||||
};
|
||||
|
||||
g.entities.insert(
|
||||
symbol_name.clone(),
|
||||
crate::models::Entity {
|
||||
name: symbol_name.clone(),
|
||||
entity_type: symbol_type,
|
||||
observations: vec![format!(
|
||||
"AST definition: {} chars",
|
||||
chunk_code.len()
|
||||
)],
|
||||
namespace: "global".to_string(),
|
||||
file_path: Some(file_str.clone()),
|
||||
created_at: Some(now),
|
||||
updated_at: Some(now),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let file_str = path.to_string_lossy().to_string();
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
|
||||
let chunks_count = chunks.len();
|
||||
let file_str_clone = file_str.clone();
|
||||
state.modify_graph(|g| {
|
||||
// Ensure File entity exists
|
||||
g.entities.entry(file_str.clone()).or_insert_with(|| {
|
||||
crate::models::Entity {
|
||||
name: file_str.clone(),
|
||||
entity_type: "File".to_string(),
|
||||
namespace: "global".to_string(),
|
||||
file_path: Some(file_str.clone()),
|
||||
created_at: Some(now),
|
||||
updated_at: Some(now),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
for (chunk_name, chunk_code, chunk_desc) in chunks {
|
||||
let symbol_name = format!("{}::{}", file_str, chunk_name);
|
||||
let symbol_type = if chunk_desc.contains("struct") {
|
||||
"DataStructure".to_string()
|
||||
} else {
|
||||
"McpTool".to_string()
|
||||
};
|
||||
|
||||
g.entities.insert(
|
||||
symbol_name.clone(),
|
||||
crate::models::Entity {
|
||||
name: symbol_name.clone(),
|
||||
entity_type: symbol_type,
|
||||
observations: vec![format!(
|
||||
"AST definition: {} chars",
|
||||
chunk_code.len()
|
||||
)],
|
||||
namespace: "global".to_string(),
|
||||
file_path: Some(file_str.clone()),
|
||||
created_at: Some(now),
|
||||
updated_at: Some(now),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let rel = crate::models::Relation {
|
||||
from: file_str.clone(),
|
||||
to: symbol_name,
|
||||
relation_type: "declares".to_string(),
|
||||
namespace: "global".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
if !g.relations.contains(&rel) {
|
||||
g.relations.push(rel);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _ = state.event_bus_tx.send(crate::state::GenericEvent {
|
||||
topic: "ast:symbol_updated".to_string(),
|
||||
session_id: None,
|
||||
payload: serde_json::json!({
|
||||
"file": file_str_clone,
|
||||
"extension": ext,
|
||||
"symbols_count": chunks_count,
|
||||
}),
|
||||
});
|
||||
|
||||
let _ = state.event_bus_tx.send(crate::state::GenericEvent {
|
||||
topic: "resource:updated".to_string(),
|
||||
session_id: None,
|
||||
payload: serde_json::json!({
|
||||
"uri": "memory://graph"
|
||||
}),
|
||||
});
|
||||
let rel = crate::models::Relation {
|
||||
from: file_str.clone(),
|
||||
to: symbol_name,
|
||||
relation_type: "declares".to_string(),
|
||||
namespace: "global".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
if !g.relations.contains(&rel) {
|
||||
g.relations.push(rel);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _ = state.event_bus_tx.send(crate::state::GenericEvent {
|
||||
topic: "ast:symbol_updated".to_string(),
|
||||
session_id: None,
|
||||
payload: serde_json::json!({
|
||||
"file": file_str_clone,
|
||||
"extension": ext,
|
||||
"symbols_count": chunks_count,
|
||||
}),
|
||||
});
|
||||
|
||||
let _ = state.event_bus_tx.send(crate::state::GenericEvent {
|
||||
topic: "resource:updated".to_string(),
|
||||
session_id: None,
|
||||
payload: serde_json::json!({
|
||||
"uri": "memory://graph"
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"Autonomous review & incremental AST index complete for {:?}",
|
||||
|
||||
Reference in new issue
Block a user