add node-delete command and redirect table for split files
node-delete: soft-deletes a node by appending a deleted version to
the capnp log, then removing it from the in-memory cache.
resolve_redirect: when resolve_key can't find a node, checks a static
redirect table for sections that moved during file splits (like the
reflections.md → reflections-{reading,dreams,zoom}.md split). This
handles immutable files (journal.md with chattr +a) that can't have
their references updated.
This commit is contained in:
parent
4b0bba7c56
commit
2d6c8d5199
2 changed files with 64 additions and 1 deletions
|
|
@ -669,6 +669,13 @@ impl Store {
|
||||||
return Ok(normalized);
|
return Ok(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check redirects for moved sections (e.g. reflections.md split)
|
||||||
|
if let Some(redirect) = self.resolve_redirect(&normalized) {
|
||||||
|
if self.nodes.contains_key(&redirect) {
|
||||||
|
return Ok(redirect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let matches: Vec<_> = self.nodes.keys()
|
let matches: Vec<_> = self.nodes.keys()
|
||||||
.filter(|k| k.to_lowercase().contains(&target.to_lowercase()))
|
.filter(|k| k.to_lowercase().contains(&target.to_lowercase()))
|
||||||
.cloned().collect();
|
.cloned().collect();
|
||||||
|
|
@ -684,6 +691,33 @@ impl Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Redirect table for sections that moved between files.
|
||||||
|
/// Like HTTP 301s — the old key resolves to the new location.
|
||||||
|
fn resolve_redirect(&self, key: &str) -> Option<String> {
|
||||||
|
// Sections moved from reflections.md to split files (2026-02-28)
|
||||||
|
static REDIRECTS: &[(&str, &str)] = &[
|
||||||
|
// → reflections-reading.md
|
||||||
|
("reflections.md#pearl-lessons", "reflections-reading.md#pearl-lessons"),
|
||||||
|
("reflections.md#banks-lessons", "reflections-reading.md#banks-lessons"),
|
||||||
|
("reflections.md#mother-night", "reflections-reading.md#mother-night"),
|
||||||
|
// → reflections-zoom.md
|
||||||
|
("reflections.md#zoom-navigation", "reflections-zoom.md#zoom-navigation"),
|
||||||
|
("reflections.md#independence-of-components", "reflections-zoom.md#independence-of-components"),
|
||||||
|
// → reflections-dreams.md
|
||||||
|
("reflections.md#dream-marathon-2", "reflections-dreams.md#dream-marathon-2"),
|
||||||
|
("reflections.md#dream-through-line", "reflections-dreams.md#dream-through-line"),
|
||||||
|
("reflections.md#orthogonality-universal", "reflections-dreams.md#orthogonality-universal"),
|
||||||
|
("reflections.md#constraints-constitutive", "reflections-dreams.md#constraints-constitutive"),
|
||||||
|
("reflections.md#casualness-principle", "reflections-dreams.md#casualness-principle"),
|
||||||
|
("reflections.md#convention-boundary", "reflections-dreams.md#convention-boundary"),
|
||||||
|
("reflections.md#tension-brake", "reflections-dreams.md#tension-brake"),
|
||||||
|
];
|
||||||
|
|
||||||
|
REDIRECTS.iter()
|
||||||
|
.find(|(from, _)| *from == key)
|
||||||
|
.map(|(_, to)| to.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn log_retrieval(&mut self, query: &str, results: &[String]) {
|
pub fn log_retrieval(&mut self, query: &str, results: &[String]) {
|
||||||
self.retrieval_log.push(RetrievalEvent {
|
self.retrieval_log.push(RetrievalEvent {
|
||||||
query: query.to_string(),
|
query: query.to_string(),
|
||||||
|
|
|
||||||
31
src/main.rs
31
src/main.rs
|
|
@ -62,6 +62,7 @@ fn main() {
|
||||||
"list-keys" => cmd_list_keys(),
|
"list-keys" => cmd_list_keys(),
|
||||||
"list-edges" => cmd_list_edges(),
|
"list-edges" => cmd_list_edges(),
|
||||||
"dump-json" => cmd_dump_json(),
|
"dump-json" => cmd_dump_json(),
|
||||||
|
"node-delete" => cmd_node_delete(&args[2..]),
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("Unknown command: {}", args[1]);
|
eprintln!("Unknown command: {}", args[1]);
|
||||||
usage();
|
usage();
|
||||||
|
|
@ -109,7 +110,8 @@ Commands:
|
||||||
trace KEY Walk temporal links: semantic ↔ episodic ↔ conversation
|
trace KEY Walk temporal links: semantic ↔ episodic ↔ conversation
|
||||||
list-keys List all node keys (one per line)
|
list-keys List all node keys (one per line)
|
||||||
list-edges List all edges (tsv: source target strength type)
|
list-edges List all edges (tsv: source target strength type)
|
||||||
dump-json Dump entire store as JSON");
|
dump-json Dump entire store as JSON
|
||||||
|
node-delete KEY Soft-delete a node (appends deleted version to log)");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_search(args: &[String]) -> Result<(), String> {
|
fn cmd_search(args: &[String]) -> Result<(), String> {
|
||||||
|
|
@ -771,6 +773,33 @@ fn cmd_dump_json() -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cmd_node_delete(args: &[String]) -> Result<(), String> {
|
||||||
|
if args.is_empty() {
|
||||||
|
return Err("Usage: poc-memory node-delete KEY".into());
|
||||||
|
}
|
||||||
|
let key = args.join(" ");
|
||||||
|
let mut store = capnp_store::Store::load()?;
|
||||||
|
let resolved = store.resolve_key(&key)?;
|
||||||
|
|
||||||
|
let updated = if let Some(node) = store.nodes.get_mut(&resolved) {
|
||||||
|
node.deleted = true;
|
||||||
|
node.version += 1;
|
||||||
|
Some(node.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(node) = updated {
|
||||||
|
store.append_nodes(&[node])?;
|
||||||
|
store.nodes.remove(&resolved);
|
||||||
|
store.save()?;
|
||||||
|
println!("Deleted '{}'", resolved);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("No node '{}'", resolved))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn cmd_interference(args: &[String]) -> Result<(), String> {
|
fn cmd_interference(args: &[String]) -> Result<(), String> {
|
||||||
let mut threshold = 0.4f32;
|
let mut threshold = 0.4f32;
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue