Skip to main content

sparrow/cmd_handlers/
handle_memory_graph_cmd.rs

1// src/cmd_handlers/handle_memory_graph_cmd.rs
2use super::prelude::*;
3pub fn handle_memory_graph(
4    action: sparrow::cli::GraphAction,
5    memory: &Arc<dyn Memory>,
6) -> anyhow::Result<()> {
7    use sparrow::memory::{GraphDirection, GraphEdge, GraphNode};
8    let now = chrono::Utc::now().to_rfc3339();
9    match action {
10        sparrow::cli::GraphAction::UpsertNode {
11            id,
12            label,
13            kind,
14            properties,
15        } => {
16            let properties = parse_json_properties(&properties)?;
17            memory.upsert_graph_node(GraphNode {
18                id: id.clone(),
19                label,
20                kind,
21                properties,
22                created_at: now.clone(),
23                updated_at: now,
24            })?;
25            println!("Graph node stored: {}", id);
26        }
27        sparrow::cli::GraphAction::UpsertEdge {
28            from_id,
29            relation,
30            to_id,
31            id,
32            weight,
33            properties,
34        } => {
35            let edge_id = id.unwrap_or_else(|| format!("{}:{}:{}", from_id, relation, to_id));
36            let properties = parse_json_properties(&properties)?;
37            memory.upsert_graph_edge(GraphEdge {
38                id: edge_id.clone(),
39                from_id,
40                to_id,
41                relation,
42                weight,
43                properties,
44                created_at: now.clone(),
45                updated_at: now,
46            })?;
47            println!("Graph edge stored: {}", edge_id);
48        }
49        sparrow::cli::GraphAction::Get { id } => {
50            if let Some(node) = memory.graph_node(&id) {
51                println!("{}", serde_json::to_string_pretty(&node)?);
52            } else {
53                println!("Graph node '{}' not found.", id);
54            }
55        }
56        sparrow::cli::GraphAction::Neighbors {
57            id,
58            direction,
59            limit,
60        } => {
61            let rows = memory.graph_neighbors(&id, GraphDirection::parse(&direction), limit);
62            println!("{}", serde_json::to_string_pretty(&rows)?);
63        }
64        sparrow::cli::GraphAction::Search { query, limit } => {
65            let nodes = memory.search_graph(&query, limit);
66            println!("{}", serde_json::to_string_pretty(&nodes)?);
67        }
68        sparrow::cli::GraphAction::Export => {
69            println!("{}", serde_json::to_string_pretty(&memory.graph_export())?);
70        }
71        sparrow::cli::GraphAction::DeleteNode { id } => {
72            memory.delete_graph_node(&id)?;
73            println!("Graph node deleted: {}", id);
74        }
75        sparrow::cli::GraphAction::DeleteEdge { id } => {
76            memory.delete_graph_edge(&id)?;
77            println!("Graph edge deleted: {}", id);
78        }
79        sparrow::cli::GraphAction::SyncNeo4j => {
80            let graph = memory.graph_export();
81            let runtime = tokio::runtime::Builder::new_current_thread()
82                .enable_all()
83                .build()?;
84            let statements =
85                runtime.block_on(sparrow::tools::knowledge_graph::sync_graph_to_neo4j(&graph))?;
86            println!(
87                "Synced graph to Neo4j: {} nodes, {} edges, {} statements",
88                graph.nodes.len(),
89                graph.edges.len(),
90                statements
91            );
92        }
93    }
94    Ok(())
95}
96
97pub fn parse_json_properties(raw: &str) -> anyhow::Result<serde_json::Value> {
98    let value: serde_json::Value = serde_json::from_str(raw)
99        .map_err(|e| anyhow::anyhow!("properties must be valid JSON object: {}", e))?;
100    if !value.is_object() {
101        anyhow::bail!("properties must be a JSON object");
102    }
103    Ok(value)
104}
105
106// ─── JSON NDJSON run ────────────────────────────────────────────────────────────
107
108pub fn current_repo_head() -> Option<String> {
109    let output = std::process::Command::new("git")
110        .args(["rev-parse", "HEAD"])
111        .output()
112        .ok()?;
113    if !output.status.success() {
114        return None;
115    }
116    let head = String::from_utf8_lossy(&output.stdout).trim().to_string();
117    if head.is_empty() { None } else { Some(head) }
118}