Skip to content

Feature: opt-in global-basename wikilink resolution for Obsidian-convention vaults #972

@matnotkin

Description

@matnotkin

Problem

When gbrain extract links ingests [[wikilinks]] from markdown files, the resolver only matches by:

  1. Parallel directory + same basename (e.g., a link from originals/foo.md resolves [[bar]] to originals/bar.md)
  2. Same directory + sibling basename (e.g., a link from concepts/foo.md resolves [[bar]] to concepts/bar.md)

It does not do a global-basename lookup. So a wikilink like [[struktura]] written in concepts/knowledge-graph.md does not resolve to projects/struktura.md, even though that page exists, has a unique basename in the vault, and resolves correctly in Obsidian.

This silently drops cross-directory bare-basename wikilinks. They're not flagged as broken — they're just missing from gbrain's graph layer.

Repro

# Set up a minimal vault with cross-directory link
mkdir -p /tmp/vault/{concepts,projects}
cat > /tmp/vault/projects/struktura.md <<EOF
---
title: Struktura
type: project
---
A project page.
EOF
cat > /tmp/vault/concepts/knowledge-graph.md <<EOF
---
title: Knowledge Graph
type: concept
---
This concept relates to [[struktura]].
EOF

gbrain init --force --dir /tmp/test-brain
GBRAIN_HOME=/tmp/test-brain gbrain import /tmp/vault
GBRAIN_HOME=/tmp/test-brain gbrain extract links
GBRAIN_HOME=/tmp/test-brain gbrain stats
# Links: 0 (expected: 1)

Obsidian's graph view correctly links the two nodes. Gbrain's graph layer does not.

Why it matters

For users who follow Obsidian's standard convention (flat-namespace wikilinks, globally-unique basenames), this silently drops most of their interlinking from gbrain's graph layer. We hit this on a fresh wiki: 71 wikilinks total across 20 pages in Obsidian (avg 3.55/page, dense galaxy), but gbrain's extract links only produced 12 — all of which were same-directory. The remaining ~60 cross-directory bare-basename links were dropped silently.

Symptoms downstream:

  • gbrain graph <slug> returns thin or missing edges that exist in the source files
  • gbrain backlinks <slug> undercounts incoming references
  • Graph-traversal queries (e.g., "what concepts touch this project") return incomplete neighbors
  • Semantic search via gbrain query is unaffected (works on chunks/embeddings, not the graph layer) — but anyone relying on graph traversal gets a partial view

Why it's probably intentional (and what to do anyway)

Gbrain's parent/ancestor resolution suggests it's designed for monorepo-style knowledge bases where basename collisions across folders are common, and path disambiguation is necessary. That's a defensible design choice for one user population. But Obsidian convention is the opposite — flat namespace, globally-unique basenames, bare wikilinks everywhere. The two assumptions clash.

Proposed: add an opt-in "global basename" resolution mode

# gbrain.yml or config
link_resolution:
  mode: ancestor          # current default — backward compatible
  # OR
  mode: global_basename   # treat bare wikilinks as referring to any uniquely-named page in the vault

In global_basename mode, resolveSlug("struktura", from_page) would:

  1. Try parent/ancestor lookup first (current behavior)
  2. Fall back to: search allSlugs for any slug whose basename matches
  3. If exactly one match → resolve to it
  4. If multiple matches → ambiguous, drop with a warning (so the user knows to disambiguate via path prefix)
  5. If zero matches → drop silently (existing behavior)

Default stays ancestor for back-compat. Users with flat-namespace vaults flip to global_basename in their config.

Alternative — auto-detect from gbrain doctor

gbrain doctor could count bare-basename wikilinks that fail to resolve under ancestor mode but WOULD resolve under global mode, and surface that as a hint:

ℹ️  Detected 60 wikilinks that would resolve under global_basename mode but currently drop.
   Your vault appears to use Obsidian-flat-namespace convention.
   Set `link_resolution.mode: global_basename` in gbrain.yml to enable.

Severity

Medium. Doesn't break anything (semantic search works fine), but silently undercounts the graph for Obsidian-convention users, leading to thin/wrong graph traversal results. Easy upstream fix; current workarounds (path-prefixed wikilinks everywhere) are ugly enough that most users would rather accept the gap than adopt them.

Affected file

src/core/link-extraction.ts — the resolveSlug function around line 160-192.

Diagnosed by an agent

This was caught by an LLM agent (Hermes-driven Echo) doing read-after-write verification on a freshly-seeded wiki, then reading gbrain's source to confirm the resolver mechanism. Worth noting as evidence that the verify-before-done discipline catches real architectural gaps that wouldn't show up in CLI smoke tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions