organize: fix # quoting, protect journal entries
Keys containing # are now pre-quoted in all cluster output (similarity scores, hub analysis, node headers) so the agent copies them correctly into bash commands. Prompt strengthened with CRITICAL warning about # being a shell comment character. Journal entries included in clusters but identified by node_type (EpisodicSession) rather than key prefix, and tagged [JOURNAL — no delete] in the output. Prompt rule 3b tells agent to LINK/REFINE journals but never DELETE them. Digest nodes (daily/weekly/monthly) still excluded entirely from clusters. Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
4cacfa7599
commit
f063eb01f0
2 changed files with 44 additions and 21 deletions
|
|
@ -172,15 +172,22 @@ fn resolve(
|
|||
return Some(Resolved { text: "(no term provided)".into(), keys: vec![] });
|
||||
}
|
||||
let term_lower = term.to_lowercase();
|
||||
let skip_prefixes = ["journal#", "daily-", "weekly-", "monthly-", "_",
|
||||
"deep-index#", "facts-", "irc-history#"];
|
||||
use crate::store::NodeType;
|
||||
|
||||
let mut cluster: Vec<(String, String)> = Vec::new();
|
||||
let mut cluster: Vec<(String, String, bool)> = Vec::new(); // (key, content, is_journal)
|
||||
for (key, node) in &store.nodes {
|
||||
if node.deleted { continue; }
|
||||
if !key.to_lowercase().contains(&term_lower) { continue; }
|
||||
if skip_prefixes.iter().any(|p| key.starts_with(p)) { continue; }
|
||||
cluster.push((key.clone(), node.content.clone()));
|
||||
// Skip digest/system nodes entirely
|
||||
match node.node_type {
|
||||
NodeType::EpisodicDaily | NodeType::EpisodicWeekly
|
||||
| NodeType::EpisodicMonthly => continue,
|
||||
_ => {}
|
||||
}
|
||||
// Skip internal prefixes
|
||||
if key.starts_with('_') { continue; }
|
||||
let is_journal = node.node_type == NodeType::EpisodicSession;
|
||||
cluster.push((key.clone(), node.content.clone(), is_journal));
|
||||
}
|
||||
cluster.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
|
|
@ -189,8 +196,15 @@ fn resolve(
|
|||
cluster.truncate(20);
|
||||
}
|
||||
|
||||
// Similarity pairs
|
||||
let pairs = crate::similarity::pairwise_similar(&cluster, 0.4);
|
||||
// Similarity pairs (need (key, content) tuples)
|
||||
let pair_input: Vec<(String, String)> = cluster.iter()
|
||||
.map(|(k, c, _)| (k.clone(), c.clone())).collect();
|
||||
let pairs = crate::similarity::pairwise_similar(&pair_input, 0.4);
|
||||
|
||||
// Helper: shell-quote keys containing #
|
||||
let sq = |k: &str| -> String {
|
||||
if k.contains('#') { format!("'{}'", k) } else { k.to_string() }
|
||||
};
|
||||
|
||||
let mut text = format!("### Cluster: '{}' ({} nodes)\n\n", term, cluster.len());
|
||||
|
||||
|
|
@ -198,14 +212,14 @@ fn resolve(
|
|||
if !pairs.is_empty() {
|
||||
text.push_str("#### Similarity scores\n\n");
|
||||
for (a, b, sim) in &pairs {
|
||||
text.push_str(&format!(" [{:.3}] {} ↔ {}\n", sim, a, b));
|
||||
text.push_str(&format!(" [{:.3}] {} ↔ {}\n", sim, sq(a), sq(b)));
|
||||
}
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
// Connectivity
|
||||
let cluster_keys: std::collections::HashSet<&str> = cluster.iter()
|
||||
.map(|(k,_)| k.as_str()).collect();
|
||||
.map(|(k,_,_)| k.as_str()).collect();
|
||||
let mut best_hub: Option<(&str, usize)> = None;
|
||||
for key in &cluster_keys {
|
||||
let intra = graph.neighbor_keys(key).iter()
|
||||
|
|
@ -216,12 +230,12 @@ fn resolve(
|
|||
}
|
||||
}
|
||||
if let Some((hub, deg)) = best_hub {
|
||||
text.push_str(&format!("#### Hub: {} (intra-cluster degree {})\n\n", hub, deg));
|
||||
text.push_str(&format!("#### Hub: {} (intra-cluster degree {})\n\n", sq(hub), deg));
|
||||
let hub_nbrs = graph.neighbor_keys(hub);
|
||||
for key in &cluster_keys {
|
||||
if *key == hub { continue; }
|
||||
if !hub_nbrs.contains(*key) {
|
||||
text.push_str(&format!(" NOT linked to hub: {}\n", key));
|
||||
text.push_str(&format!(" NOT linked to hub: {}\n", sq(key)));
|
||||
}
|
||||
}
|
||||
text.push('\n');
|
||||
|
|
@ -230,9 +244,11 @@ fn resolve(
|
|||
// Full node contents
|
||||
text.push_str("#### Node contents\n\n");
|
||||
let mut result_keys = Vec::new();
|
||||
for (key, content) in &cluster {
|
||||
for (key, content, is_journal) in &cluster {
|
||||
let words = content.split_whitespace().count();
|
||||
text.push_str(&format!("##### {} ({} words)\n\n{}\n\n---\n\n", key, words, content));
|
||||
let tag = if *is_journal { " [JOURNAL — no delete]" } else { "" };
|
||||
text.push_str(&format!("##### {}{} ({} words)\n\n{}\n\n---\n\n",
|
||||
sq(key), tag, words, content));
|
||||
result_keys.push(key.clone());
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue