# Wikilinks

How `[[…]]` text inside an Obsidian note becomes a graph edge.

## Syntax surface

The parser recognises four forms:

| Form | Components | Example |
|------|-----------|---------|
| Bare | target | `[[Adam]]` |
| With anchor | target, anchor | `[[Project Maxy#Goals]]` |
| With alias | target, alias | `[[Adam Langley\|Adam]]` |
| Embed | target (routed to attachments OR page-embed) | `![[Diagram.png]]` |

`anchor` is the heading slug after `#`; `alias` is the display text after `|`. The parser records both alongside the resolved target so the resulting edge carries the original linkText, alias, and anchor as properties.

## Code-fence isolation

`[[…]]` inside fenced code blocks (` ``` ` or `~~~`) is NOT parsed. Operators paste code samples into notes; markdown inside those samples should not become graph edges. The fence-aware scan splits the body into code and non-code segments before regex matching.

## Resolution priority

For every wikilink, the parser computes two candidate sets and chooses by these rules:

1. **Intra-vault page candidates.** Match the target text against (a) page titles (basename without `.md`) and (b) declared aliases (frontmatter `aliases:`). Both lookups are case-insensitive after NFC normalisation. A path-form target (`Folder/Sub/Page` or `Folder/Page.md`) bypasses title/alias lookup and resolves directly to that exact path if it exists.
2. **Existing entity candidates.** Query `:Person`, `:Organization`, `:Concept` nodes scoped to the account where `name = target` (exact, distance 0) or where `levenshtein(name, target) ≤ 2` (fuzzy fallback). The entity index is built once per dry-run from the live graph state.

Decision matrix:

| Page hits | Exact entity hits | Fuzzy entity hits | Outcome |
|-----------|-------------------|-------------------|---------|
| 1 | 0 | 0 | Resolved: page |
| 0 | 1 | — | Resolved: entity (exact) |
| 0 | 0 | 1 | Resolved: entity (fuzzy) |
| 0 | 0 | 0 | Resolved: stub (`:Concept`) |
| ≥1 | ≥1 | — | **Ambiguous** — operator decides |
| ≥2 | 0 | — | **Ambiguous** — operator decides |
| 0 | ≥2 | — | **Ambiguous** — operator decides |
| 0 | 0 | ≥2 | **Ambiguous** — operator decides |

Ambiguities surface in the dry-run plan with a stable `id`. The operator picks one outcome per ambiguity; that decision flows into the commit phase via the `resolutions` map.

## Edge kinds

| Resolved kind | Edge | Cypher target |
|---------------|------|---------------|
| Page | `:WIKILINKS_TO` | `(:KnowledgeDocument)-[:WIKILINKS_TO]->(:KnowledgeDocument)` |
| Entity | `:MENTIONS` | `(:KnowledgeDocument)-[:MENTIONS]->(:Person\|:Organization\|:Concept)` |
| Stub | `:MENTIONS` | `(:KnowledgeDocument)-[:MENTIONS]->(:Concept {source:'obsidian-stub'})` |

Every edge carries `linkText`, `alias`, `anchor` (page edges only), `source='obsidian'`, `createdAt`.

## Why stubs exist

A wikilink to "Aaron" where no vault page and no graph entity matches still represents the operator's intent: this note is about Aaron, whoever Aaron is. Dropping the edge would lose information; demanding a hard match would force the operator to pre-populate the graph before importing.

Stubs are explicitly distinguishable: `:Concept {source:'obsidian-stub'}`. A later session can promote a stub to a typed `:Person` or `:Organization` via `memory-update`, at which point the `:MENTIONS` edge remains pointed at the same node and the stub flag is removed.

## Self-references

`[[Self]]` inside `Self.md` produces a self-edge. Obsidian treats it as valid; the parser preserves it.

## Anchors

`[[Page#section]]` resolves on `Page` only — the `section` portion is stored on the edge as `anchor`. The parser does not create per-heading nodes; section decomposition is out of scope for vault pages (they are atomic notes).

## Embeds vs wikilinks

`![[file]]` with an attachment extension (`.png`, `.pdf`, `.mp3`, etc.) is treated as an attachment, not a wikilink. `![[Another Note]]` (no attachment extension) is treated as a wikilink — Obsidian renders it as a transclusion, and the cross-reference is preserved as a `:WIKILINKS_TO` / `:MENTIONS` edge depending on resolution.
