From 31ab6127a805f005e96ce23f998e40dba5c1d3bf Mon Sep 17 00:00:00 2001 From: "Dr. Fabian Wilde" Date: Fri, 9 Oct 2026 01:37:47 +0200 Subject: [PATCH 1/2] fix(pending): mark malformed JSON names as incomplete --- rust/lore-core/src/pending.rs | 61 ++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/rust/lore-core/src/pending.rs b/rust/lore-core/src/pending.rs index 938870f..1daaaa0 100644 --- a/rust/lore-core/src/pending.rs +++ b/rust/lore-core/src/pending.rs @@ -59,8 +59,11 @@ pub fn for_sessions(cfg: &Config, req: &Value) -> Result { }).collect::>>()?; let _namespace = namespace_lock(cfg)?; let mut complete = true; - let pending_ids = match ids(cfg) { - Ok(ids) => ids, + let pending_ids = match scan_ids(cfg) { + Ok((ids, invalid_stem)) => { + if invalid_stem { complete = false; } + ids + }, Err(Error::TooLarge) => { complete = false; Vec::new() }, Err(error) => return Err(error), }; @@ -201,16 +204,22 @@ fn visible(item: &Value, slug: &str) -> bool { gate::pending_project(item).is_none_or(|project| project == slug) } pub fn ids(cfg: &Config) -> Result> { + Ok(scan_ids(cfg)?.0) +} +/// Keep malformed JSON names visible as uncertainty to source-scoped summaries. +/// A bounded directory scan is still shared with ordinary pending ID listings. +fn scan_ids(cfg: &Config) -> Result<(Vec, bool)> { let dir = cfg.root.join("pending"); if fs::symlink_metadata(&dir).is_err_and(|error| error.kind() == std::io::ErrorKind::NotFound) { - return Ok(Vec::new()); + return Ok((Vec::new(), false)); } let mut ids = Vec::new(); + let mut invalid_stem = false; for name in files::directory_names(&dir, 4096)? { let path = dir.join(name); if path .extension() - .is_some_and(|extension| extension == "json") + .is_some_and(|extension| extension == "json") || path.file_name().is_some_and(|name| name == ".json") { if let Some(id) = path .file_stem() @@ -218,11 +227,13 @@ pub fn ids(cfg: &Config) -> Result> { .filter(|id| valid_id(id)) { ids.push(id.to_owned()); + } else { + invalid_stem = true; } } } ids.sort(); - Ok(ids) + Ok((ids, invalid_stem)) } fn scrub_display(value: &Value, depth: usize) -> Result { if depth > 32 { @@ -1181,6 +1192,46 @@ mod tests { assert_eq!(result["sessions"][0]["complete"], false); } #[test] + fn invalid_json_stems_make_every_session_summary_incomplete() { + let temp = tempfile::tempdir().unwrap(); + let cfg = Config::for_root(temp.path().join("lore")); + let cwd = temp.path().join("source"); + let pid = gate::stage(&cfg, &json!({"kind":"memory","scope":"user", + "source_project":project_slug(&cwd),"session_id":"one","text":"known proposal"}), &auth()).unwrap(); + let request = json!({"cwd":cwd,"session_ids":["one","two"]}); + assert_eq!(for_sessions(&cfg, &request).unwrap()["complete"], true); + let dir = cfg.root.join("pending"); + for name in ["bad stem.json", ".json"] { + fs::write(dir.join(name), b"{}").unwrap(); + let result = for_sessions(&cfg, &request).unwrap(); + assert_eq!(result["complete"], false, "{name}"); + assert_eq!(result["sessions"][0]["complete"], false, "{name}"); + assert_eq!(result["sessions"][1]["complete"], false, "{name}"); + assert_eq!(result["sessions"][0]["pending_pids"], json!([pid]), "{name}"); + assert_eq!(result["sessions"][1]["pending_pids"], json!([]), "{name}"); + fs::remove_file(dir.join(name)).unwrap(); + assert_eq!(for_sessions(&cfg, &request).unwrap()["complete"], true); + } + } + #[cfg(unix)] + #[test] + fn non_utf8_json_stem_makes_every_session_summary_incomplete() { + use std::os::unix::ffi::OsStringExt; + let temp = tempfile::tempdir().unwrap(); + let cfg = Config::for_root(temp.path().join("lore")); + let request = json!({"cwd":temp.path(),"session_ids":["one","two"]}); + assert_eq!(for_sessions(&cfg, &request).unwrap()["complete"], true); + let dir = cfg.root.join("pending"); + files::private_dir(&dir).unwrap(); + let name = std::ffi::OsString::from_vec(b"bad\xff.json".to_vec()); + fs::write(dir.join(&name), b"{}").unwrap(); + let result = for_sessions(&cfg, &request).unwrap(); + assert_eq!(result["complete"], false); + assert!(result["sessions"].as_array().unwrap().iter().all(|row| row["complete"] == false)); + fs::remove_file(dir.join(name)).unwrap(); + assert_eq!(for_sessions(&cfg, &request).unwrap()["complete"], true); + } + #[test] fn summary_waits_for_writer_and_refuses_an_unreadable_entry() { use std::sync::mpsc; use std::time::Duration; From 0bbf7bebdbcf49073bf7006b7d6d2f725e0adda0 Mon Sep 17 00:00:00 2001 From: "Dr. Fabian Wilde" Date: Fri, 9 Oct 2026 01:39:31 +0200 Subject: [PATCH 2/2] chore(release): prepare LORE 0.62.19 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- CHANGELOG.md | 6 ++++++ Cargo.lock | 2 +- plugin.json | 2 +- rust/lore-core/Cargo.toml | 2 +- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index b64a541..1acb676 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "lore", "source": "./", "description": "Shared memory for Claude Code, Codex, and DOXA: capped curated facts, evidence-backed beliefs, FTS5 session search, staged review, learned skills, and signed sync.", - "version": "0.62.18" + "version": "0.62.19" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e034181..5813f25 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "lore", "description": "Memory for Claude Code that reasons about you and improves itself: Honcho-pattern belief store with deriver, dreamer and dialectic (/lore:ask), automatic skillification with a closed judge/update/retire loop, on a Hermes-pattern core \u2014 hard-capped curated memory, FTS5 session search, staged review.", - "version": "0.62.18", + "version": "0.62.19", "author": { "name": "Fabian Wilde", "email": "docwilde@proton.me" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d0a875..d6dae67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.62.19 — 2026-10-09 + +**Pending summary completeness** + +- Fix **`pending_for_sessions_v1`** reporting `complete: true` and zero pending when `pending/*.json` has an invalid or non-UTF-8 filename stem. These entries now make every requested session incomplete; valid IDs remain visible. + ## 0.62.18 — 2026-10-09 **Session-scoped pending review** diff --git a/Cargo.lock b/Cargo.lock index 2f0a997..eddc371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -612,7 +612,7 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lore-core" -version = "0.62.18" +version = "0.62.19" dependencies = [ "fancy-regex", "hmac", diff --git a/plugin.json b/plugin.json index 78f77d0..cb5ccc7 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "lore", - "version": "0.62.18", + "version": "0.62.19", "description": "Unified user, repo, and session memory for Claude Code, Codex, and DOXA", "author": { "name": "Dr. Fabian Wilde", diff --git a/rust/lore-core/Cargo.toml b/rust/lore-core/Cargo.toml index 57e7cb6..c01a6bb 100644 --- a/rust/lore-core/Cargo.toml +++ b/rust/lore-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lore-core" -version = "0.62.18" +version = "0.62.19" edition = "2021" license = "AGPL-3.0-only" description = "Native LORE memory, beliefs, review and session index"