query: rich QueryResult + toolkit cleanup

QueryResult carries a fields map (BTreeMap<String, Value>) so callers
don't re-resolve fields after queries run. Neighbors queries inject
edge context (strength, rel_type) at construction time.

New public API:
- run_query(): parse + execute + format in one call
- format_value(): format a Value for display
- execute_parsed(): internal, avoids double-parse in run_query

Removed: output_stages(), format_field()

Simplified commands:
- cmd_query, cmd_graph, cmd_link, cmd_list_keys all delegate to run_query
- cmd_experience_mine uses existing find_current_transcript()

Deduplication:
- now_epoch() 3 copies → 1 (capnp_store's public fn)
- hub_threshold → Graph::hub_threshold() method
- eval_node + eval_edge → single eval() with closure for field resolution
- compare() collapsed via Ordering (35 → 15 lines)

Modernization:
- 12 sites of partial_cmp().unwrap_or(Ordering::Equal) → total_cmp()
This commit is contained in:
ProofOfConcept 2026-03-03 12:07:04 -05:00
parent 64d2b441f0
commit fa7fe8c14b
7 changed files with 187 additions and 264 deletions

View file

@ -318,16 +318,9 @@ fn cmd_status() -> Result<(), String> {
fn cmd_graph() -> Result<(), String> {
let store = capnp_store::Store::load()?;
let g = store.build_graph();
println!("Top nodes by degree:");
let results = query::execute_query(
&store, &g, "* | sort degree | limit 10")?;
for r in &results {
let deg = g.degree(&r.key);
let cc = g.clustering_coefficient(&r.key);
println!(" {:40} deg={:3} cc={:.3}", r.key, deg, cc);
}
Ok(())
query::run_query(&store, &g,
"* | sort degree | limit 10 | select degree,clustering_coefficient")
}
fn cmd_used(args: &[String]) -> Result<(), String> {
@ -486,14 +479,9 @@ fn cmd_link(args: &[String]) -> Result<(), String> {
let store = capnp_store::Store::load()?;
let resolved = store.resolve_key(&key)?;
let g = store.build_graph();
println!("Neighbors of '{}':", resolved);
let neighbors = g.neighbors(&resolved);
for (i, (n, strength)) in neighbors.iter().enumerate() {
let cc = g.clustering_coefficient(n);
println!(" {:2}. [{:.2}] {} (cc={:.3})", i + 1, strength, n, cc);
}
Ok(())
query::run_query(&store, &g,
&format!("neighbors('{}') | select strength,clustering_coefficient", resolved))
}
fn cmd_replay_queue(args: &[String]) -> Result<(), String> {
@ -843,29 +831,7 @@ fn cmd_experience_mine(args: &[String]) -> Result<(), String> {
let jsonl_path = if let Some(path) = args.first() {
path.clone()
} else {
// Find the most recent JSONL transcript
let projects_dir = std::path::Path::new(&std::env::var("HOME").unwrap_or_default())
.join(".claude/projects");
let mut entries: Vec<(std::time::SystemTime, std::path::PathBuf)> = Vec::new();
if let Ok(dirs) = std::fs::read_dir(&projects_dir) {
for dir in dirs.flatten() {
if let Ok(files) = std::fs::read_dir(dir.path()) {
for file in files.flatten() {
let path = file.path();
if path.extension().map_or(false, |ext| ext == "jsonl") {
if let Ok(meta) = file.metadata() {
if let Ok(mtime) = meta.modified() {
entries.push((mtime, path));
}
}
}
}
}
}
}
entries.sort_by(|a, b| b.0.cmp(&a.0));
entries.first()
.map(|(_, p)| p.to_string_lossy().to_string())
find_current_transcript()
.ok_or("no JSONL transcripts found")?
};
@ -1222,11 +1188,7 @@ fn cmd_spectral_suggest(args: &[String]) -> Result<(), String> {
fn cmd_list_keys() -> Result<(), String> {
let store = capnp_store::Store::load()?;
let g = store.build_graph();
let results = query::execute_query(&store, &g, "* | sort key asc")?;
for r in &results {
println!("{}", r.key);
}
Ok(())
query::run_query(&store, &g, "* | sort key asc")
}
fn cmd_list_edges() -> Result<(), String> {
@ -1637,44 +1599,5 @@ Pipe stages:\n \
let query_str = args.join(" ");
let store = capnp_store::Store::load()?;
let graph = store.build_graph();
let stages = query::output_stages(&query_str)?;
let results = query::execute_query(&store, &graph, &query_str)?;
// Check for count stage
if stages.iter().any(|s| matches!(s, query::Stage::Count)) {
println!("{}", results.len());
return Ok(());
}
if results.is_empty() {
eprintln!("No results");
return Ok(());
}
// Check for select stage
let fields: Option<&Vec<String>> = stages.iter().find_map(|s| match s {
query::Stage::Select(f) => Some(f),
_ => None,
});
if let Some(fields) = fields {
let mut header = vec!["key".to_string()];
header.extend(fields.iter().cloned());
println!("{}", header.join("\t"));
for r in &results {
let mut row = vec![r.key.clone()];
for f in fields {
row.push(query::format_field(f, &r.key, &store, &graph));
}
println!("{}", row.join("\t"));
}
} else {
for r in &results {
println!("{}", r.key);
}
}
Ok(())
query::run_query(&store, &graph, &query_str)
}