Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.26"
"version": "0.62.27"
}
]
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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.26",
"version": "0.62.27",
"author": {
"name": "Fabian Wilde",
"email": "docwilde@proton.me"
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.62.27 — 2026-11-12

**Startup memory**

- Keep curated memory available in startup and refresh snapshots when pending
proposal enumeration fails. Report the pending count as unknown with its
diagnostic code. Pending listings retain their bounded scan and review checks.

## 0.62.26 — 2026-11-12

**Empty memory replacements**
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "lore",
"version": "0.62.26",
"version": "0.62.27",
"description": "Unified user, repo, and session memory for Claude Code, Codex, and DOXA",
"author": {
"name": "Dr. Fabian Wilde",
Expand Down
2 changes: 1 addition & 1 deletion rust/lore-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lore-core"
version = "0.62.26"
version = "0.62.27"
edition = "2021"
license = "AGPL-3.0-only"
description = "Native LORE memory, beliefs, review and session index"
Expand Down
50 changes: 46 additions & 4 deletions rust/lore-core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,19 @@ pub fn build_context(
parts.push(String::new());
}
}
let pending = crate::pending::ids(cfg)?.len();
if pending > 0 {
parts.push(format!("{pending} staged proposal(s) from background review — surface this to the user once early in the session and suggest pending review."));
parts.push(String::new());
match crate::pending::ids(cfg) {
Ok(ids) if !ids.is_empty() => {
parts.push(format!("{} staged proposal(s) from background review — surface this to the user once early in the session and suggest pending review.", ids.len()));
parts.push(String::new());
}
Ok(_) => {}
Err(error) => {
parts.push(format!(
"Pending review status unavailable ({}); pending count is unknown.",
error.code()
));
parts.push(String::new());
}
}
if matches!(scope, "all" | "project") {
let count = crate::store::connect(cfg)
Expand Down Expand Up @@ -676,6 +685,39 @@ mod tests {
assert!(graph_awareness_op(&cfg, &json!({})).unwrap().is_null());
}
#[test]
fn pending_scan_overflow_keeps_curated_context_and_reports_an_unknown_count() {
let temp = tempfile::tempdir().unwrap();
let cfg = Config::for_root(temp.path().join("lore"));
memory::write_entries(
&cfg.root.join("USER.md"),
&["Curated memory survives a pending backlog".into()],
1000,
)
.unwrap();
let render = || build_context(
&cfg,
temp.path(),
"all",
"claude",
RefreshPolicy { interval_secs: None, on_change: true },
);
let pending = cfg.root.join("pending");
files::private_dir(&pending).unwrap();
for index in 0..3 {
fs::write(pending.join(format!("fixture-{index}.json")), b"{}").unwrap();
}
assert!(render().unwrap().contains("3 staged proposal(s)"));
for index in 3..4097 {
fs::write(pending.join(format!("fixture-{index}.json")), b"{}").unwrap();
}
assert_eq!(crate::pending::ids(&cfg), Err(Error::TooLarge));
let context = render().unwrap();
assert!(context.contains("Curated memory survives a pending backlog"));
assert!(context.contains("Pending review status unavailable (output_too_large)"));
assert!(!context.contains("staged proposal(s) from background review"));
assert_eq!(fs::read_dir(&pending).unwrap().count(), 4097);
}
#[test]
fn context_keeps_filemap_body_and_other_host_facts_out_of_snapshot() {
let temp = tempfile::tempdir().unwrap();
let cfg = Config::for_root(temp.path().join("lore"));
Expand Down
37 changes: 37 additions & 0 deletions rust/lore-core/tests/native_admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,43 @@ fn native_hook_injection_never_calls_provider() {
.unwrap()
.contains("Hook fixture memory"));
}
#[test]
fn native_startup_hook_keeps_memory_when_pending_scan_exceeds_its_limit() {
let (t, cfg) = config();
let pending = cfg.root.join("pending");
lore_core::files::private_dir(&pending).unwrap();
std::fs::write(cfg.root.join("USER.md"), "- Startup memory survives overflow\n").unwrap();
for index in 0..4097 {
std::fs::write(pending.join(format!("fixture-{index}.json")), b"{}").unwrap();
}
for engine in ["claude", "codex"] {
let mut child = Command::new(env!("CARGO_BIN_EXE_lore-rs"))
.args(["hook", "--engine", engine, "--event", "session-start"])
.env_clear()
.env("HOME", t.path())
.env("LORE_ROOT", &cfg.root)
.env("LORE_DISABLE_SYNC", "1")
.env("LORE_CLAUDE_BIN", "/this/provider/must/never/be/launched")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.unwrap();
use std::io::Write;
write!(child.stdin.as_mut().unwrap(), "{}", json!({"cwd":t.path(),"session_id":"overflow-fixture"})).unwrap();
drop(child.stdin.take());
let output = child.wait_with_output().unwrap();
assert!(output.status.success(), "{engine}: {}", String::from_utf8_lossy(&output.stderr));
assert!(output.stderr.is_empty());
let frame: Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(frame["hookSpecificOutput"]["hookEventName"], "SessionStart");
let context = frame["hookSpecificOutput"]["additionalContext"].as_str().unwrap();
assert!(context.contains("Startup memory survives overflow"));
assert!(context.contains("Pending review status unavailable (output_too_large)"));
assert_eq!(lore_core::pending::ids(&cfg), Err(lore_core::Error::TooLarge));
assert_eq!(std::fs::read_dir(&pending).unwrap().count(), 4097);
}
}
fn interactive() -> Authority {
Authority::Interactive {
agent: "fixture-reviewer".into(),
Expand Down
Loading