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.20"
"version": "0.62.21"
}
]
}
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.20",
"version": "0.62.21",
"author": {
"name": "Fabian Wilde",
"email": "docwilde@proton.me"
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.62.21 — 2026-10-12

**Reviewed Python codegraph snapshots**

- Allow Python paths through **`codegraph_snapshot_read_v1`** and **`codegraph_snapshot_store_v1`**, retaining exact-source rehash checks.
- Keep Python snapshot binding `unknown`: syntax candidates do not prove semantic imports, calls, or name resolution.

## 0.62.20 — 2026-10-09

**Reviewed codegraph snapshots**
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.

11 changes: 8 additions & 3 deletions docs/codegraph-snapshots.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

LORE can retain one reviewed DOXA syntax answer per exact Git worktree,
file, and query kind. This is a local, data-only cache. It does not resolve
Rust imports or calls, turn syntax candidates into facts, sync snapshots,
Rust or Python imports or calls, turn syntax candidates into facts, sync snapshots,
or put codegraph data into an agent's context.

## Protocol

`codegraph_snapshot_read_v1` accepts
`{"op":"codegraph_snapshot_read_v1","cwd":"/absolute/worktree", "query":"file|imports|calls|modules","path":"src/lib.rs"}`.
`codegraph_snapshot_read_v1` accepts a Rust path such as
`{"op":"codegraph_snapshot_read_v1","cwd":"/absolute/worktree", "query":"file|imports|calls|modules","path":"src/lib.rs"}`
or a Python path such as
`{"op":"codegraph_snapshot_read_v1","cwd":"/absolute/worktree", "query":"file|imports|calls|modules","path":"src/main.py"}`.
The requested path must be a repository-relative `.rs` or `.py` file no longer
than 4096 bytes. Source reads remain bounded to 1 MiB and are rehashed on read
and before and after a store.
It returns `{"status":"missing"}` or a `current` result with the stored
`graph`, revision, source and graph SHA-256, and explicit
`binding: "unknown"`, `freshness: "requested_source_verified_only"`.
Expand Down
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.20",
"version": "0.62.21",
"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.20"
version = "0.62.21"
edition = "2021"
license = "AGPL-3.0-only"
description = "Native LORE memory, beliefs, review and session index"
Expand Down
41 changes: 36 additions & 5 deletions rust/lore-core/src/codegraph_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn query(req: &Value) -> Result<(&str, &str)> {
.filter(|kind| matches!(*kind, "file" | "imports" | "calls" | "modules"))
.ok_or(Error::InvalidRequest)?;
let path = req["path"].as_str().ok_or(Error::InvalidRequest)?;
if path.is_empty() || path.len() > 4096 || !path.ends_with(".rs")
if path.is_empty() || path.len() > 4096 || !(path.ends_with(".rs") || path.ends_with(".py"))
|| !Path::new(path).components().all(|part| matches!(part, Component::Normal(_)))
|| path.chars().any(char::is_control) {
return Err(Error::InvalidRequest);
Expand Down Expand Up @@ -226,15 +226,19 @@ mod tests {
use std::fs;

fn fixture() -> (tempfile::TempDir, Config, PathBuf, Value) {
fixture_with_source("lib.rs", "fn one() {}\n")
}

fn fixture_with_source(path: &str, source: &str) -> (tempfile::TempDir, Config, PathBuf, Value) {
let temp = tempfile::tempdir().unwrap();
let root = temp.path().join("repo");
fs::create_dir(&root).unwrap();
fs::create_dir(root.join(".git")).unwrap();
fs::write(root.join(".git/config"), "[core]\n").unwrap();
fs::write(root.join("lib.rs"), "fn one() {}\n").unwrap();
fs::write(root.join(path), source).unwrap();
let cfg = Config::for_root(temp.path().join("store"));
let sha = source_hash(&root, "lib.rs").unwrap();
let graph = json!({"scope":root,"query":"file","value":"lib.rs","status":"ok","coverage":{},
let sha = source_hash(&root, path).unwrap();
let graph = json!({"scope":root,"query":"file","value":path,"status":"ok","coverage":{},
"requested_source_sha256":sha,"requested_source_read_unix_ms":1,
"rows":[],"edges":[],"module_edges":[]});
let snapshot = json!({"schema_version":1,"storage":"export_only_not_persisted",
Expand All @@ -244,7 +248,10 @@ mod tests {
(temp, cfg, root, snapshot)
}
fn req(root: &Path, snapshot: &Value, expected: u64) -> Value {
json!({"cwd":root,"query":"file","path":"lib.rs","expected_revision":expected,"snapshot":snapshot})
req_for(root, snapshot, expected, "lib.rs")
}
fn req_for(root: &Path, snapshot: &Value, expected: u64, path: &str) -> Value {
json!({"cwd":root,"query":"file","path":path,"expected_revision":expected,"snapshot":snapshot})
}
fn owner() -> Authority { Authority::HumanReview { agent: "fixture".into(), engine: "codex".into() } }

Expand All @@ -267,6 +274,22 @@ mod tests {
assert_eq!(store(&cfg, &next, &owner()), Err(Error::Changed));
}

#[test]
fn python_snapshot_roundtrips_and_source_edits_invalidate_it() {
let (_temp,cfg,root,mut snapshot)=fixture_with_source("main.py","def one():\n return 1\n");
snapshot["graph"]["coverage"]=json!({"language":"python","parser":"fixture"});
snapshot["graph"]["rows"]=json!([{"kind":"function","name":"one","path":"main.py"}]);
let request=req_for(&root,&snapshot,0,"main.py");
assert_eq!(read(&cfg,&request).unwrap()["status"],"missing");
assert_eq!(store(&cfg,&request,&owner()).unwrap()["revision"],1);
let stored=read(&cfg,&request).unwrap();
assert_eq!(stored["status"],"current");
assert_eq!(stored["path"],"main.py");
assert_eq!(stored["graph"]["rows"][0]["name"],"one");
fs::write(root.join("main.py"),"def one():\n return 2\n").unwrap();
assert_eq!(read(&cfg,&request),Err(Error::Changed));
}

#[test]
fn different_or_recreated_worktree_cannot_reuse_a_snapshot() {
let (_temp, cfg, root, snapshot) = fixture();
Expand All @@ -293,6 +316,14 @@ mod tests {
let mut request = req(&root, &snapshot, 0);
request["path"] = json!("../lib.rs");
assert_eq!(store(&cfg, &request, &owner()), Err(Error::InvalidRequest));
request = req(&root, &snapshot, 0);
request["path"] = json!("main.js");
assert_eq!(store(&cfg, &request, &owner()), Err(Error::InvalidRequest));
request["path"] = json!("../main.py");
assert_eq!(store(&cfg, &request, &owner()), Err(Error::InvalidRequest));
request["path"] = json!("/workspace/main.py");
assert_eq!(store(&cfg, &request, &owner()), Err(Error::InvalidRequest));
assert_eq!(read(&cfg, &request), Err(Error::InvalidRequest));
request = req(&root.join("..").join("repo"), &snapshot, 0);
assert_eq!(read(&cfg, &request), Err(Error::Changed));
request = req(&root, &snapshot, 0);
Expand Down
Loading