mirror of
https://github.com/akitaonrails/ai-memory.git
synced 2026-10-02 03:24:46 +08:00
Merge PR #915 into main
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
This commit is contained in:
@@ -190,6 +190,18 @@ fn log_bounded(value: &str) -> String {
|
||||
ai_memory_core::truncate_utf8_bytes(value, RELATION_LOG_FIELD_MAX_BYTES)
|
||||
}
|
||||
|
||||
/// Whether a link target's final path segment can name a page.
|
||||
///
|
||||
/// A directory target (trailing `/`, so an empty last segment) and a
|
||||
/// stem-less `.md` cannot: the first used to normalize to the literal
|
||||
/// `notes/.md` in `relations:` frontmatter, the second to an
|
||||
/// extension-less path — both permanently unresolved `links` rows that no
|
||||
/// page write could ever repoint.
|
||||
fn last_segment_names_a_page(target: &str) -> bool {
|
||||
let last = target.rsplit_once('/').map_or(target, |(_, s)| s);
|
||||
!last.is_empty() && last != ".md"
|
||||
}
|
||||
|
||||
/// Extract typed relation edges from a page's `relations:` frontmatter
|
||||
/// (2.0 item 3):
|
||||
///
|
||||
@@ -225,17 +237,18 @@ pub fn extract_relation_links(frontmatter: &serde_json::Value) -> Vec<LinkTarget
|
||||
},
|
||||
};
|
||||
// Same terminal normalization as wikilinks: extension-less
|
||||
// targets gain `.md`; anything with a non-md extension is
|
||||
// not a page and is skipped.
|
||||
// targets gain `.md`; a directory target, a stem-less `.md`, and
|
||||
// anything with a non-md extension are not pages and are skipped.
|
||||
let raw_path = raw_path.trim();
|
||||
let last = raw_path.rsplit_once('/').map_or(raw_path, |(_, s)| s);
|
||||
let names_a_page = last_segment_names_a_page(raw_path)
|
||||
&& (!last.contains('.') || raw_path.ends_with(".md"));
|
||||
if !names_a_page {
|
||||
tracing::warn!(target = %log_bounded(target), "relation target is not a page; skipping");
|
||||
continue;
|
||||
}
|
||||
let normalized = if last.contains('.') {
|
||||
if raw_path.ends_with(".md") {
|
||||
raw_path.to_string()
|
||||
} else {
|
||||
tracing::warn!(target = %log_bounded(target), "relation target is not a page; skipping");
|
||||
continue;
|
||||
}
|
||||
raw_path.to_string()
|
||||
} else {
|
||||
format!("{raw_path}.md")
|
||||
};
|
||||
@@ -368,13 +381,20 @@ fn normalize_link_target(raw: &str, page_path: &PagePath, wikilink: bool) -> Opt
|
||||
return None;
|
||||
}
|
||||
|
||||
// A directory target (`notes/`) is not a page: kept, it minted an
|
||||
// extension-less `to_path` (or, for a wikilink, the literal
|
||||
// `notes/.md`) that no page write could ever resolve.
|
||||
if !last_segment_names_a_page(target) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut target = target.to_string();
|
||||
let last_segment = target.rsplit_once('/').map_or(target.as_str(), |(_, s)| s);
|
||||
if last_segment.contains('.') {
|
||||
if !target.ends_with(".md") {
|
||||
return None;
|
||||
}
|
||||
} else if wikilink || !last_segment.is_empty() {
|
||||
} else {
|
||||
target.push_str(".md");
|
||||
}
|
||||
|
||||
@@ -496,6 +516,21 @@ mod tests {
|
||||
assert_eq!(links[0].path.as_str(), "notes/ok.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_and_stemless_targets_are_not_pages() {
|
||||
// `sessions/` used to normalize to the literal `sessions/.md` — a
|
||||
// link no page write could ever resolve; the same trailing-slash
|
||||
// form in the body stayed extension-less, which cannot match a page
|
||||
// path either. Both are dropped now.
|
||||
let fm = serde_json::json!({
|
||||
"relations": {"fixes": ["sessions/", "sessions/.md"]}
|
||||
});
|
||||
assert!(extract_relation_links(&fm).is_empty());
|
||||
|
||||
assert!(extract_links("- [notes/](notes/)\n", &page()).is_empty());
|
||||
assert!(extract_links("- [[notes/]]\n", &page()).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pages_without_relations_extract_nothing() {
|
||||
assert!(extract_relation_links(&serde_json::json!({})).is_empty());
|
||||
|
||||
Reference in New Issue
Block a user