use std::path::PathBuf;
use agileplus_sqlite::Storage;
use anyhow::{Context, Result};
use clap::Args;
#[derive(Debug, Args)]
#[command(name = "store")]
pub struct StoreCommand {
#[arg(short, long)]
input: PathBuf,
#[arg(short, long)]
db: PathBuf,
#[arg(long)]
id: Option<String>,
}
impl StoreCommand {
pub fn run(&self) -> Result<()> {
let json = std::fs::read_to_string(&self.input)
.with_context(|| format!("Failed to read input: {}", self.input.display()))?;
let graph: agileplus_domain::intent_graph::IntentGraph = serde_json::from_str(&json)
.with_context(|| format!("Failed to parse JSON from: {}", self.input.display()))?;
let id = match &self.id {
Some(s) if !s.is_empty() => s.clone(),
_ => graph_id_from_json(&json),
};
let storage = Storage::open(&self.db)
.with_context(|| format!("Failed to open storage at: {}", self.db.display()))?;
storage
.save_graph(&id, &graph)
.with_context(|| format!("Failed to persist graph {id}"))?;
println!(
"Stored graph {id} ({} nodes, {} edges) at {}",
graph.nodes.len(),
graph.edges.len(),
self.db.display(),
);
Ok(())
}
}
fn graph_id_from_json(json: &str) -> String {
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x00000100000001B3;
let mut hash = FNV_OFFSET;
for byte in json.as_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
format!("graph-{:016x}", hash)
}