Problem
When gbrain extract links ingests [[wikilinks]] from markdown files, the resolver only matches by:
- Parallel directory + same basename (e.g., a link from
originals/foo.md resolves [[bar]] to originals/bar.md)
- 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:
- Try parent/ancestor lookup first (current behavior)
- Fall back to: search
allSlugs for any slug whose basename matches
- If exactly one match → resolve to it
- If multiple matches → ambiguous, drop with a warning (so the user knows to disambiguate via path prefix)
- 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.
Problem
When
gbrain extract linksingests[[wikilinks]]from markdown files, the resolver only matches by:originals/foo.mdresolves[[bar]]tooriginals/bar.md)concepts/foo.mdresolves[[bar]]toconcepts/bar.md)It does not do a global-basename lookup. So a wikilink like
[[struktura]]written inconcepts/knowledge-graph.mddoes not resolve toprojects/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
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 linksonly 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 filesgbrain backlinks <slug>undercounts incoming referencesgbrain queryis unaffected (works on chunks/embeddings, not the graph layer) — but anyone relying on graph traversal gets a partial viewWhy 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
In
global_basenamemode,resolveSlug("struktura", from_page)would:allSlugsfor any slug whose basename matchesDefault stays
ancestorfor back-compat. Users with flat-namespace vaults flip toglobal_basenamein their config.Alternative — auto-detect from
gbrain doctorgbrain doctorcould count bare-basename wikilinks that fail to resolve under ancestor mode but WOULD resolve under global mode, and surface that as a hint: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— theresolveSlugfunction 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.