From 57e26c7e76b34c924b8045ec6fcfaf89d0b3598c Mon Sep 17 00:00:00 2001 From: "Dr. Fabian Wilde" Date: Sun, 11 Oct 2026 17:19:35 +0200 Subject: [PATCH 1/2] feat(codegraph): allow reviewed Python snapshots --- docs/codegraph-snapshots.md | 11 +++++-- rust/lore-core/src/codegraph_snapshot.rs | 41 +++++++++++++++++++++--- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/docs/codegraph-snapshots.md b/docs/codegraph-snapshots.md index 15161c0..03ef345 100644 --- a/docs/codegraph-snapshots.md +++ b/docs/codegraph-snapshots.md @@ -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"`. diff --git a/rust/lore-core/src/codegraph_snapshot.rs b/rust/lore-core/src/codegraph_snapshot.rs index b925dae..8a57a7b 100644 --- a/rust/lore-core/src/codegraph_snapshot.rs +++ b/rust/lore-core/src/codegraph_snapshot.rs @@ -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); @@ -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", @@ -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() } } @@ -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(); @@ -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); From 67b3a483ae166efa45e26c3b5fd92808d5331eaa Mon Sep 17 00:00:00 2001 From: "Dr. Fabian Wilde" Date: Mon, 12 Oct 2026 12:08:29 +0200 Subject: [PATCH 2/2] release(lore): 0.62.21 Python codegraph snapshots --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- CHANGELOG.md | 7 +++++++ Cargo.lock | 2 +- plugin.json | 2 +- rust/lore-core/Cargo.toml | 2 +- 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 4458dca..04ff28d 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.20" + "version": "0.62.21" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 02fe02b..1ae8de6 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.20", + "version": "0.62.21", "author": { "name": "Fabian Wilde", "email": "docwilde@proton.me" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4602a75..956a0af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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** diff --git a/Cargo.lock b/Cargo.lock index adfdf36..14813d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -612,7 +612,7 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lore-core" -version = "0.62.20" +version = "0.62.21" dependencies = [ "fancy-regex", "hmac", diff --git a/plugin.json b/plugin.json index 28da014..c8d86c9 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.20", + "version": "0.62.21", "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 7ab3b27..d9fe6e4 100644 --- a/rust/lore-core/Cargo.toml +++ b/rust/lore-core/Cargo.toml @@ -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"