fix(markdown): resolve [[wikilinks]] vault-wide, not sibling-only - #2415
fix(markdown): resolve [[wikilinks]] vault-wide, not sibling-only#2415Phantomn wants to merge 1 commit into
Conversation
…aphify-Labs#2413) Obsidian resolves [[name]] against the whole vault by filename, but _resolve_markdown_link only ever tried source_dir / name. In a vault that splits notes into type folders (Concepts/, Entities/, Guides/, Maps/), every cross-folder wikilink therefore missed: no target_file stamp was written, the Graphify-Labs#2169 remap pass had nothing to canonicalize, and build_from_json dropped the edge. Same failure shape as Graphify-Labs#2211, but the target is not in the sibling directory at all, so the stamp is never produced in the first place. Sibling resolution still runs first and still wins, so path-qualified links ([text](./other.md), which are relative by spec) keep exactly their current semantics and never enter the new path. Only a wikilink whose sibling guess missed is stamped with its bare name; extract()'s post-pass is the first point that knows every in-root file, so it builds a stem index there and repoints those edges. Ambiguity resolves deterministically: the linking note's own folder first (mirrors Obsidian), then shallowest path, then lexicographic. The stamp is transient and popped in the same pass, so graph.json shape is unchanged, and a link to a file that exists nowhere still dangles as before. Measured on a 499-note Obsidian vault: valid references 1,293 -> 2,323 against an authored baseline of 2,339 (55.3% -> 99.3% recall); graph edges 7,623 -> 8,470. The residual 16 are links to files that genuinely do not exist. Tests: 4 new cases covering the stamp, the path-qualified non-regression, cross-folder resolution with a still-dangling broken link, and same-folder preference on an ambiguous stem.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR adds vault-wide resolution for Obsidian-style [[name]] wikilinks in the markdown extractor. Previously such links were resolved only against the linking note's sibling directory; now when a sibling match isn't found, markdown.py stamps the edge with a wikilink_name, and a new post-pass in extract.py repoints those edges by building a filename index across all in-root document files (with same-folder/shallowest/lexicographic tiebreaking). Path-qualified markdown links keep their existing relative-resolution behavior, and the transient stamp is removed before results are returned. The surface area includes the new add_edge/add_link parameters in markdown.py, the new resolution block in extract.py's extract(), and several new tests in tests/test_languages.py covering stamping, path-qualified links, cross-folder resolution, and same-folder ambiguity preference.
Worth a look
- name_index built only from remap_paths, missing unchanged in-root files on incremental runs —
graphify/extract.py:4983· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- wikilink_name stamp left on edges for source files outside root —
graphify/extract.py:4987· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1498 functions depend on the 633 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 361 callers, 29 callees
Verification — 1498 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1370 function(s) in the blast radius were not formally verified this run
· 1 more finding(s) on lines outside this diff (see the check run).
Fixes #2413.
Problem
[[wikilinks]]were resolved sibling-only. Obsidian resolves[[name]]againstthe whole vault by filename, so in a vault organized by note type (
Concepts/,Entities/,Guides/,Maps/) every cross-folder link missed: notarget_filestamp was written, the #2169 remap pass had nothing to canonicalize, and
build_from_jsondropped the edge.Same failure shape as #2211 (fixed in v0.9.28 for the incremental case), but here
the target is not in the sibling directory at all, so the stamp is never produced
in the first place.
Approach
Sibling resolution still runs first and still wins. Only a wikilink whose sibling
guess missed is stamped with its bare name;
extract()'s post-pass — thefirst point that knows every in-root file — builds a
stem -> [rel path]indexand repoints those edges.
[text](./other.md)is relative byspec, never gets the stamp, and never enters the new code path. Covered by a
dedicated non-regression test.
(mirrors Obsidian), then shallowest path, then lexicographic.
wikilink_nameis popped in the same pass, sograph.jsonshape is unchanged.
[[name]]pointing at a file that existsnowhere behaves exactly as before.
Measured impact
Real Obsidian vault, 499 notes, against
v8HEAD (0.9.32):referencesThe residual 16 are links to files that genuinely do not exist in the vault.
Tests
4 new cases in
tests/test_languages.py:test_markdown_wikilink_stamps_name_when_sibling_missing— stamp appears onlyfor the non-sibling target; the sibling one resolves normally
test_markdown_path_qualified_link_is_not_stamped— relative-linknon-regression
test_extract_resolves_wikilinks_across_folders— cross-folder link resolves,a genuinely missing target still dangles, no stamp leaks to the caller
test_extract_wikilink_prefers_same_folder_on_ambiguity— same-stem tie-breakFull suite is green against the same baseline as
v8HEAD:3,891 passedwiththe identical 16 pre-existing
test_skillgen.pyfailures present before and afterthis change (they come from a shallow clone lacking the skill sources, unrelated
to this patch).