query by NodeType instead of key prefix
Replace key prefix matching (journal#j-, daily-, weekly-, monthly-) with NodeType filters (EpisodicSession, EpisodicDaily, EpisodicWeekly, EpisodicMonthly) for all queries: journal-tail, digest gathering, digest auto-detection, experience mining dedup, and find_journal_node. Add EpisodicMonthly to NodeType enum and capnp schema. Key naming conventions (journal#j-TIMESTAMP-slug, daily-DATE, etc.) are retained for key generation — the fix is about how we find nodes, not how we name them. Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
parent
fd5591653d
commit
804578b977
8 changed files with 43 additions and 47 deletions
|
|
@ -145,15 +145,13 @@ fn gather(level: &DigestLevel, store: &Store, arg: &str) -> Result<(String, Vec<
|
|||
.collect();
|
||||
load_child_digests(store, child_name, &child_labels)
|
||||
} else {
|
||||
// Leaf level: scan store for journal entries matching label
|
||||
let date_re = Regex::new(&format!(
|
||||
r"^journal#j-{}", regex::escape(&label)
|
||||
)).unwrap();
|
||||
// Leaf level: scan store for episodic entries matching date
|
||||
let mut entries: Vec<_> = store.nodes.values()
|
||||
.filter(|n| date_re.is_match(&n.key))
|
||||
.filter(|n| n.node_type == store::NodeType::EpisodicSession
|
||||
&& n.timestamp > 0
|
||||
&& store::format_date(n.timestamp) == label)
|
||||
.map(|n| {
|
||||
let ts = n.key.strip_prefix("journal#j-").unwrap_or(&n.key);
|
||||
(ts.to_string(), n.content.clone())
|
||||
(store::format_datetime(n.timestamp), n.content.clone())
|
||||
})
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
|
@ -252,14 +250,10 @@ pub fn generate(store: &mut Store, level_name: &str, arg: &str) -> Result<(), St
|
|||
pub fn digest_auto(store: &mut Store) -> Result<(), String> {
|
||||
let today = Local::now().format("%Y-%m-%d").to_string();
|
||||
|
||||
// Collect all dates with journal entries
|
||||
let date_re = Regex::new(r"^\d{4}-\d{2}-\d{2}").unwrap();
|
||||
let dates: Vec<String> = store.nodes.keys()
|
||||
.filter_map(|key| {
|
||||
key.strip_prefix("journal#j-")
|
||||
.filter(|rest| rest.len() >= 10 && date_re.is_match(rest))
|
||||
.map(|rest| rest[..10].to_string())
|
||||
})
|
||||
// Collect all dates with episodic entries
|
||||
let dates: Vec<String> = store.nodes.values()
|
||||
.filter(|n| n.node_type == store::NodeType::EpisodicSession && n.timestamp > 0)
|
||||
.map(|n| store::format_date(n.timestamp))
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
|
@ -408,10 +402,12 @@ fn parse_digest_node_links(key: &str, content: &str) -> Vec<DigestLink> {
|
|||
pub fn parse_all_digest_links(store: &Store) -> Vec<DigestLink> {
|
||||
let mut all_links = Vec::new();
|
||||
|
||||
let mut digest_keys: Vec<&String> = store.nodes.keys()
|
||||
.filter(|k| k.starts_with("daily-")
|
||||
|| k.starts_with("weekly-")
|
||||
|| k.starts_with("monthly-"))
|
||||
let mut digest_keys: Vec<&String> = store.nodes.iter()
|
||||
.filter(|(_, n)| matches!(n.node_type,
|
||||
store::NodeType::EpisodicDaily
|
||||
| store::NodeType::EpisodicWeekly
|
||||
| store::NodeType::EpisodicMonthly))
|
||||
.map(|(k, _)| k)
|
||||
.collect();
|
||||
digest_keys.sort();
|
||||
|
||||
|
|
|
|||
|
|
@ -336,21 +336,11 @@ pub fn experience_mine(
|
|||
.map(|n| n.content.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Get recent journal entries to avoid duplication
|
||||
let key_date_re = Regex::new(r"^journal#j-(\d{4}-\d{2}-\d{2}[t-]\d{2}-\d{2})").unwrap();
|
||||
let date_re = Regex::new(r"(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2})").unwrap();
|
||||
// Get recent episodic entries to avoid duplication
|
||||
let mut journal: Vec<_> = store.nodes.values()
|
||||
.filter(|node| node.key.starts_with("journal#j-"))
|
||||
.filter(|node| matches!(node.node_type, store::NodeType::EpisodicSession))
|
||||
.collect();
|
||||
journal.sort_by(|a, b| {
|
||||
let ak = key_date_re.captures(&a.key).map(|c| c[1].to_string())
|
||||
.or_else(|| date_re.captures(&a.content).map(|c| c[1].to_string()))
|
||||
.unwrap_or_default();
|
||||
let bk = key_date_re.captures(&b.key).map(|c| c[1].to_string())
|
||||
.or_else(|| date_re.captures(&b.content).map(|c| c[1].to_string()))
|
||||
.unwrap_or_default();
|
||||
ak.cmp(&bk)
|
||||
});
|
||||
journal.sort_by_key(|n| n.timestamp);
|
||||
let recent: String = journal.iter().rev().take(10)
|
||||
.map(|n| format!("---\n{}\n", n.content))
|
||||
.collect();
|
||||
|
|
|
|||
30
src/main.rs
30
src/main.rs
|
|
@ -1122,7 +1122,8 @@ fn cmd_trace(args: &[String]) -> Result<(), String> {
|
|||
episodic_session.push(entry),
|
||||
store::NodeType::EpisodicDaily =>
|
||||
episodic_daily.push(entry),
|
||||
store::NodeType::EpisodicWeekly =>
|
||||
store::NodeType::EpisodicWeekly
|
||||
| store::NodeType::EpisodicMonthly =>
|
||||
episodic_weekly.push(entry),
|
||||
store::NodeType::Semantic =>
|
||||
semantic.push(entry),
|
||||
|
|
@ -1855,12 +1856,12 @@ fn cmd_journal_tail(args: &[String]) -> Result<(), String> {
|
|||
// Original journal-tail behavior
|
||||
journal_tail_entries(&store, n, full)
|
||||
} else {
|
||||
let prefix = match level {
|
||||
1 => "daily-",
|
||||
2 => "weekly-",
|
||||
_ => "monthly-",
|
||||
let node_type = match level {
|
||||
1 => store::NodeType::EpisodicDaily,
|
||||
2 => store::NodeType::EpisodicWeekly,
|
||||
_ => store::NodeType::EpisodicMonthly,
|
||||
};
|
||||
journal_tail_digests(&store, prefix, n, full)
|
||||
journal_tail_digests(&store, node_type, n, full)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1916,17 +1917,22 @@ fn journal_tail_entries(store: &store::Store, n: usize, full: bool) -> Result<()
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn journal_tail_digests(store: &store::Store, prefix: &str, n: usize, full: bool) -> Result<(), String> {
|
||||
fn journal_tail_digests(store: &store::Store, node_type: store::NodeType, n: usize, full: bool) -> Result<(), String> {
|
||||
let mut digests: Vec<_> = store.nodes.values()
|
||||
.filter(|node| node.key.starts_with(prefix))
|
||||
.filter(|node| node.node_type == node_type)
|
||||
.collect();
|
||||
// Sort by key — the date/week label sorts lexicographically
|
||||
digests.sort_by(|a, b| a.key.cmp(&b.key));
|
||||
// Sort by timestamp, fall back to key for lexicographic ordering
|
||||
digests.sort_by(|a, b| {
|
||||
if a.timestamp > 0 && b.timestamp > 0 {
|
||||
a.timestamp.cmp(&b.timestamp)
|
||||
} else {
|
||||
a.key.cmp(&b.key)
|
||||
}
|
||||
});
|
||||
|
||||
let skip = if digests.len() > n { digests.len() - n } else { 0 };
|
||||
for node in digests.iter().skip(skip) {
|
||||
let label = node.key.strip_prefix(prefix)
|
||||
.unwrap_or(&node.key);
|
||||
let label = &node.key;
|
||||
let title = extract_title(&node.content);
|
||||
if full {
|
||||
println!("--- [{}] {} ---\n{}\n", label, title, node.content);
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ fn node_type_label(nt: NodeType) -> &'static str {
|
|||
NodeType::EpisodicSession => "episodic_session",
|
||||
NodeType::EpisodicDaily => "episodic_daily",
|
||||
NodeType::EpisodicWeekly => "episodic_weekly",
|
||||
NodeType::EpisodicMonthly => "episodic_monthly",
|
||||
NodeType::Semantic => "semantic",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ impl Store {
|
|||
Some(output.trim_end().to_string())
|
||||
}
|
||||
|
||||
/// Find the journal node that best matches the given entry text.
|
||||
/// Find the episodic node that best matches the given entry text.
|
||||
pub fn find_journal_node(&self, entry_text: &str) -> Option<String> {
|
||||
if entry_text.is_empty() {
|
||||
return None;
|
||||
|
|
@ -308,7 +308,7 @@ impl Store {
|
|||
let mut best_score = 0;
|
||||
|
||||
for (key, node) in &self.nodes {
|
||||
if !key.starts_with("journal#") {
|
||||
if node.node_type != NodeType::EpisodicSession {
|
||||
continue;
|
||||
}
|
||||
let content_lower = node.content.to_lowercase();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ pub fn classify_filename(filename: &str) -> NodeType {
|
|||
let bare = filename.strip_suffix(".md").unwrap_or(filename);
|
||||
if bare.starts_with("daily-") { NodeType::EpisodicDaily }
|
||||
else if bare.starts_with("weekly-") { NodeType::EpisodicWeekly }
|
||||
else if bare.starts_with("monthly-") { NodeType::EpisodicMonthly }
|
||||
else if bare == "journal" { NodeType::EpisodicSession }
|
||||
else { NodeType::Semantic }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ pub enum NodeType {
|
|||
EpisodicDaily,
|
||||
EpisodicWeekly,
|
||||
Semantic,
|
||||
EpisodicMonthly,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
|
||||
|
|
@ -345,7 +346,7 @@ pub enum RelationType {
|
|||
}
|
||||
|
||||
capnp_enum!(NodeType, memory_capnp::NodeType,
|
||||
[EpisodicSession, EpisodicDaily, EpisodicWeekly, Semantic]);
|
||||
[EpisodicSession, EpisodicDaily, EpisodicWeekly, Semantic, EpisodicMonthly]);
|
||||
|
||||
capnp_enum!(Provenance, memory_capnp::Provenance,
|
||||
[Manual, Journal, Agent, Dream, Derived,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue