Skip to content

Commit 9bacc52

Browse files
committed
fix(cli): preserve OKF permalink resolution precedence
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 5c46f90 commit 9bacc52

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

src/basic_memory/okf/render.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def convert_wikilinks(
6363
*,
6464
include_project: bool = True,
6565
ambiguous_aliases: frozenset[str] = frozenset(),
66+
permalinks: dict[str, str] | None = None,
6667
) -> str:
6768
"""Use MarkdownIt's code/escape/link rules while retaining untouched source bytes."""
6869
body = body.replace("\r\n", "\n").replace("\r", "\n")
@@ -114,6 +115,16 @@ def wikilink(state: StateInline, silent: bool) -> bool:
114115
resolved = targets.get(relative.lstrip("/"))
115116
if resolved is None:
116117
resolved = targets.get(relative.lstrip("/") + ".md")
118+
if resolved is None and permalinks:
119+
# Semantic addresses precede title/path aliases, even when they look like filenames.
120+
for candidate in build_permalink_resolution_candidates(
121+
target, project, include_project
122+
):
123+
if target in ambiguous_aliases and candidate != target:
124+
break
125+
if candidate in permalinks:
126+
resolved = permalinks[candidate]
127+
break
117128
if resolved is None:
118129
for candidate in build_permalink_resolution_candidates(
119130
target, project, include_project
@@ -195,6 +206,7 @@ def wikilink(state: StateInline, silent: bool) -> bool:
195206
def render_bundle(snapshot: ExportSnapshot) -> tuple[ExportFile, ...]:
196207
"""Preserve frontmatter and prose; attach only semantics lost by link conversion."""
197208
targets: dict[str, str] = {}
209+
permalinks: dict[str, str] = {}
198210
ambiguous: set[str] = set()
199211
documents: dict[str, Document] = {}
200212
titles: dict[str, str] = {}
@@ -220,7 +232,7 @@ def render_bundle(snapshot: ExportSnapshot) -> tuple[ExportFile, ...]:
220232
aliases = {file.path, str(PurePosixPath(file.path).with_suffix("")), title}
221233
permalink = normalize_frontmatter_value(source_metadata.get("permalink"))
222234
if isinstance(permalink, str) and permalink:
223-
aliases.add(permalink)
235+
permalinks[permalink] = file.path
224236
for alias in aliases:
225237
if alias in targets and targets[alias] != file.path:
226238
ambiguous.add(alias)
@@ -268,6 +280,7 @@ def render_bundle(snapshot: ExportSnapshot) -> tuple[ExportFile, ...]:
268280
snapshot.project,
269281
include_project=snapshot.permalinks_include_project,
270282
ambiguous_aliases=frozenset(ambiguous),
283+
permalinks=permalinks,
271284
)
272285
content = "---\n" + yaml.safe_dump(metadata, allow_unicode=True, sort_keys=False)
273286
content += "---\n" + body

tests/okf/test_okf.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,11 @@ def test_ambiguous_bare_title_does_not_fall_through_to_filename():
497497
(
498498
ExportFile("Same.md", b"---\ntitle: Same\n---\n"),
499499
ExportFile("other.md", b"---\ntitle: Same\npermalink: same\n---\n"),
500-
ExportFile("source.md", b"[[Same]] and [[Same.md]]"),
500+
ExportFile("source.md", b"[[Same]] and [[Same.md]] and [[./Same.md]]"),
501501
),
502502
)
503503
source = next(file.content for file in render_bundle(snapshot) if file.path == "source.md")
504-
assert b"[Same](/Same) and [Same.md](/Same.md)" in source
504+
assert b"[Same](/Same) and [Same.md](/other.md) and [./Same.md](/Same.md)" in source
505505

506506

507507
@pytest.mark.parametrize(
@@ -619,3 +619,32 @@ def test_scalar_permalink_aliases_preserve_authored_metadata(yaml_permalink, tar
619619
)
620620
def test_wikilink_delimiters_use_canonical_escape_rules(body, target, expected):
621621
assert convert_wikilinks(body, "source.md", {target: "note.md"}, "p") == expected
622+
623+
624+
@pytest.mark.parametrize("permalink", ["foo.md", "foo"])
625+
def test_permalink_precedes_file_alias_but_explicit_relative_path_stays_a_path(
626+
permalink, test_project
627+
):
628+
from basic_memory.models import Entity
629+
from basic_memory.services.bulk_link_resolver import ProjectEntityIdentityIndex
630+
631+
owner = Entity(title="Owner", file_path="owner.md", permalink=permalink)
632+
index = ProjectEntityIdentityIndex.from_entities(
633+
test_project, [Entity(title="foo", file_path="foo.md"), owner]
634+
)
635+
assert (
636+
index.resolve_strict(
637+
"foo.md", include_project_permalinks=True, workspace_permalink=None
638+
).entity
639+
is owner
640+
)
641+
snapshot = ExportSnapshot(
642+
"p",
643+
(
644+
ExportFile("foo.md", b"# File"),
645+
ExportFile("owner.md", f"---\npermalink: {permalink}\n---\n# Permalink owner".encode()),
646+
ExportFile("source.md", b"[[foo.md]] and [[./foo.md]]"),
647+
),
648+
)
649+
source = next(file.content for file in render_bundle(snapshot) if file.path == "source.md")
650+
assert b"[foo.md](/owner.md) and [./foo.md](/foo.md)" in source

0 commit comments

Comments
 (0)