journal: remove all stringly-typed key patterns, use NodeType

- journal_new: key is slugified title (agent names things properly)
- journal_tail: sort by created_at (immutable), not timestamp (mutable)
- journal_update: find latest by created_at
- {{latest_journal}}: query by NodeType::EpisodicSession, not "journal" key
- poc-memory journal write: requires a name argument
- Removed all journal#j-{timestamp}-{slug} patterns from:
  - prompts.rs (rename candidates)
  - graph.rs (date extraction, organize skip list)
  - cursor.rs (date extraction)
  - store/mod.rs (doc comment)
- graph.rs organize: filter by NodeType::Semantic instead of key prefix
- cursor.rs: use created_at for date extraction instead of key parsing

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
ProofOfConcept 2026-03-26 19:11:17 -04:00
parent 85fa54cba9
commit eac59b423e
9 changed files with 63 additions and 67 deletions

View file

@ -480,12 +480,12 @@ pub fn cmd_organize(term: &str, threshold: f32, key_only: bool, create_anchor: b
let term_lower = term.to_lowercase();
let mut topic_nodes: Vec<(String, String)> = Vec::new(); // (key, content)
// Prefixes that indicate ephemeral/generated nodes to skip
let skip_prefixes = ["journal#", "daily-", "weekly-", "monthly-", "_",
"deep-index#", "facts-", "irc-history#"];
let skip_prefixes = ["_", "deep-index#", "facts-", "irc-history#"];
for (key, node) in &store.nodes {
if node.deleted { continue; }
// Skip episodic/digest nodes — use NodeType, not key prefix
if node.node_type != crate::store::NodeType::Semantic { continue; }
let key_matches = key.to_lowercase().contains(&term_lower);
let content_matches = !key_only && node.content.to_lowercase().contains(&term_lower);
if !key_matches && !content_matches { continue; }

View file

@ -1,7 +1,7 @@
// cli/journal.rs — journal subcommand handlers
pub fn cmd_tail(n: usize, full: bool, provenance: Option<&str>) -> Result<(), String> {
pub fn cmd_tail(n: usize, full: bool, provenance: Option<&str>, dedup: bool) -> Result<(), String> {
let path = crate::store::nodes_path();
if !path.exists() {
return Err("No node log found".into());
@ -24,11 +24,21 @@ pub fn cmd_tail(n: usize, full: bool, provenance: Option<&str>) -> Result<(), St
}
}
// Filter by provenance if specified (prefix match)
// Filter by provenance if specified (substring match)
if let Some(prov) = provenance {
entries.retain(|n| n.provenance.contains(prov));
}
// Dedup: keep only the latest version of each key
if dedup {
let mut seen = std::collections::HashSet::new();
// Walk backwards so we keep the latest
entries = entries.into_iter().rev()
.filter(|n| seen.insert(n.key.clone()))
.collect();
entries.reverse();
}
let start = entries.len().saturating_sub(n);
for node in &entries[start..] {
let ts = if node.timestamp > 0 && node.timestamp < 4_000_000_000 {
@ -172,27 +182,23 @@ pub fn cmd_journal_tail(n: usize, full: bool, level: u8) -> Result<(), String> {
}
}
pub fn cmd_journal_write(text: &[String]) -> Result<(), String> {
pub fn cmd_journal_write(name: &str, text: &[String]) -> Result<(), String> {
if text.is_empty() {
return Err("journal-write requires text".into());
return Err("journal write requires text".into());
}
super::check_dry_run();
let text = text.join(" ");
let timestamp = crate::store::format_datetime(crate::store::now_epoch());
let content = format!("## {}{}\n\n{}", timestamp, name, text);
let slug: String = text.split_whitespace()
.take(6)
let key: String = name.split_whitespace()
.map(|w| w.to_lowercase()
.chars().filter(|c| c.is_alphanumeric() || *c == '-')
.collect::<String>())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("-");
let slug = if slug.len() > 50 { &slug[..50] } else { &slug };
let key = format!("journal#j-{}-{}", timestamp.to_lowercase().replace(':', "-"), slug);
let content = format!("## {}\n\n{}", timestamp, text);
let source_ref = find_current_transcript();