diff --git a/chrs-graph/src/lib.rs b/chrs-graph/src/lib.rs index 44224c9b..4c60b300 100644 --- a/chrs-graph/src/lib.rs +++ b/chrs-graph/src/lib.rs @@ -44,20 +44,23 @@ impl DoltGraph { .output()?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("Dolt Command Failed: {:?} -> {}", args, stderr); return Err(GraphError::CommandFailed(stderr.to_string())); } Ok(()) } pub fn commit(&self, message: &str) -> Result<(), GraphError> { - self.run_cmd(&["add", "."])?; + self.run_cmd(&["add", "-A"])?; self.run_cmd(&["commit", "-m", message])?; Ok(()) } pub fn create_table(&self, table_name: &str, schema: &str) -> Result<(), GraphError> { let query = format!("CREATE TABLE {} ({})", table_name, schema); + println!("Executing: dolt sql -q \"{}\"", query); self.run_cmd(&["sql", "-q", &query])?; + self.commit(&format!("Create table {}", table_name))?; Ok(()) } @@ -90,6 +93,7 @@ impl DoltGraph { columns.join(", "), values.join(", ") ); + println!("Executing: dolt sql -q \"{}\"", query); self.run_cmd(&["sql", "-q", &query])?; Ok(()) } @@ -105,6 +109,5 @@ mod tests { let dir = TempDir::new().unwrap(); let graph = DoltGraph::init(dir.path()).expect("init failed"); graph.create_table("nodes", "id INT PRIMARY KEY, name TEXT").expect("create table failed"); - graph.commit("initial commit with table").expect("commit failed"); } } diff --git a/chrs-slurp/Cargo.toml b/chrs-slurp/Cargo.toml new file mode 100644 index 00000000..c2e8f4a3 --- /dev/null +++ b/chrs-slurp/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "chrs-slurp" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrs-graph = { path = "../chrs-graph" } +ucxl = { path = "../UCXL" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" +chrono = { version = "0.4", features = ["serde"] } +uuid = { version = "1.0", features = ["v4", "serde"] } + +[dev-dependencies] +tempfile = "3" + diff --git a/chrs-slurp/src/lib.rs b/chrs-slurp/src/lib.rs new file mode 100644 index 00000000..341f4fb1 --- /dev/null +++ b/chrs-slurp/src/lib.rs @@ -0,0 +1,85 @@ +use chrs_graph::{DoltGraph, GraphError}; +use ucxl::UCXLAddress; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DecisionRecord { + pub id: Uuid, + pub author: String, + pub reasoning: String, + pub citations: Vec, // Serialized UCXL addresses + pub timestamp: DateTime, +} + +#[derive(Debug, Error)] +pub enum SlurpError { + #[error("Graph error: {0}")] + Graph(#[from] GraphError), + #[error("Serialization error: {0}")] + Serde(#[from] serde_json::Error), + #[error("Validation error: {0}")] + ValidationError(String), +} + +pub struct CurationEngine { + graph: DoltGraph, +} + +impl CurationEngine { + pub fn new(graph: DoltGraph) -> Self { + Self { graph } + } + + pub fn curate_decision(&self, dr: DecisionRecord) -> Result<(), SlurpError> { + // 1. Validate Citations + for citation in &dr.citations { + use std::str::FromStr; + UCXLAddress::from_str(citation) + .map_err(|e| SlurpError::ValidationError(format!("Invalid citation {}: {}", citation, e)))?; + } + + // 2. Log DR into Graph (create table if needed handled by insert_node in future, + // but for now let's ensure it's there). + // If it fails because it exists, that's fine. + let _ = self.graph.create_table("curated_decisions", "id VARCHAR(255) PRIMARY KEY, author TEXT, reasoning TEXT, citations TEXT, curated_at TEXT"); + + let data = serde_json::json!({ + "id": dr.id.to_string(), + "author": dr.author, + "reasoning": dr.reasoning, + "citations": serde_json::to_string(&dr.citations)?, + "curated_at": dr.timestamp.to_rfc3339() + }); + + self.graph.insert_node("curated_decisions", data)?; + self.graph.commit(&format!("Curation complete for DR: {}", dr.id))?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn test_curation_flow() { + let dir = TempDir::new().unwrap(); + let graph = DoltGraph::init(dir.path()).expect("graph init failed"); + let engine = CurationEngine::new(graph); + + let dr = DecisionRecord { + id: Uuid::new_v4(), + author: "agent-001".into(), + reasoning: "Tested the implementation of SLURP.".into(), + citations: vec!["ucxl://system:watcher@local:filesystem/#/UCXL/src/lib.rs".into()], + timestamp: Utc::now(), + }; + + engine.curate_decision(dr).expect("curation failed"); + } +}