# YAML Frontmatter + Wikilinks — Design

**Date:** 2026-03-01
**Status:** Approved

## Goal

Make kbx memory files compatible with Obsidian vaults by:
1. Migrating person/project files from `**Key:** Value` markdown metadata to YAML frontmatter
2. Adding `[[wikilinks]]` throughout memory files for cross-referencing entities

## Philosophy

kbx is a smart CLI layer on top of flat markdown files in an Obsidian-like vault. Files should be
readable and navigable in any markdown tool (Obsidian, VS Code, GitHub, etc.). YAML frontmatter is
the standard metadata format. Wikilinks are the standard cross-reference format.

## Phase 1: YAML Frontmatter

### Person file format

**Before:**
```markdown
# Eric Fourrier

**Also known as:** Eric F.
**Email:** eric.fourrier@gitguardian.com
**Role:** CEO/Founder
**Team:** Executive Leadership
**Reports to:** Board/Shareholders
**Pinned:** true
**Pcm Base:** Persister (Persévérant)

## PCM Profile
...
```

**After:**
```yaml
---
aliases: [Eric F.]
email: eric.fourrier@gitguardian.com
role: CEO/Founder
team: "[[Executive Leadership]]"
reports_to: "[[Board/Shareholders]]"
pinned: true
pcm_base: Persister (Persévérant)
pcm_phase: Thinker (Analyseur)
---
# Eric Fourrier

## PCM Profile
...
```

### Project file format

**Before:**
```markdown
# AI Adoption

**Codename/Also called:** TF Agentic AI, AI Tooling Review
**Status:** Active
**Started:** November 2025
**Lead:** Jeremy Brown (CTO) — Task Force lead

## What It Is
...
```

**After:**
```yaml
---
aliases: [TF Agentic AI, AI Tooling Review]
status: Active
started: November 2025
lead: "[[Jeremy Brown]] (CTO) — Task Force lead; [[Kevin Nedelec]] — AI/Tools lead"
---
# AI Adoption

## What It Is
...
```

### Rules

- Entity-reference fields get wikilinks: `team`, `reports_to`, `lead`, `company`
- Plain value fields stay plain: `email`, `role`, `status`, `pcm_base`, `pcm_phase`
- `aliases` is a plain list (alternative names, not links)
- `pinned` only written when true
- Title stays as `# H1` heading (not in frontmatter)

### kbx code changes

| File | Change |
|------|--------|
| `entities.py` | `_parse_person_file()` / `_parse_project_file()` → read YAML frontmatter via `yaml.safe_load()` |
| `entities.py` | Add `strip_wikilinks(value)` helper — `"[[Foo]]"` → `"Foo"` for DB storage |
| `writeback.py` | `_rebuild_person_header()` / `_rebuild_project_header()` → emit YAML frontmatter |
| `crud.py` | `_build_markdown()` → YAML frontmatter format |
| `chunker.py` | Strip `[[...]]` from chunk content before indexing (FTS searches plain text) |
| `search.py` | Strip `[[...]]` from snippets |

### Migration

- One-time script: `scripts/migrate_to_yaml_frontmatter.py`
- Converts 341 person + 13 project files
- Preserves all `## body` sections verbatim
- Atomic writes (temp file → rename)

## Phase 3: Wikilinks

### Where wikilinks appear

1. **YAML frontmatter** — entity-reference fields: `"[[Entity Name]]"`
2. **Facts** — entity names in fact text: `[2026-02-23] Reports to [[Jeremy Brown]]`
3. **Body sections** — freeform content: `Led by [[Kevin Nedelec]] and [[Pierre Lalanne]]`
4. **Notes** (`memory/notes/*.md`) — same as body
5. **Meeting frontmatter** — attendees: `- "[[Jeremy Brown]]"`

### What does NOT get wikilinked

- Glossary terms (definitions, not entity references)
- The `# H1` title of the file itself
- Aliases in YAML `aliases:` field
- Content inside code blocks
- Search snippets (stripped to plain text for clean display)

### kbx implementation

- `strip_wikilinks(text)` — removes `[[` and `]]` for DB storage, FTS indexing, snippet display
- Write-time linking: `writeback.py` emits `[[Name]]` for known entities when writing files
- One-time batch script: `scripts/add_wikilinks.py` — scans all memory files, links known entities

### brief-deck implementation

| Area | Change |
|------|--------|
| **marked.js extension** | Custom renderer: `[[Entity Name]]` → `<a href="..." class="wikilink">Entity Name</a>` |
| **Entity metadata** | Strip `[[...]]` for table display, OR render as clickable links |
| **CSS** | `.wikilink` styling — subtle, distinct from external links |
| **`_extract_body_sections()`** | Handle YAML frontmatter for person/project files |
| **Search snippets** | No change — kbx strips wikilinks before returning snippets |

## Decisions

- **Wikilinks everywhere** (YAML + body), matching Obsidian convention
- **Strip in snippets** (Option A) — snippets are scannable text, not navigation surfaces
- **kbx strips for DB/search** — wikilinks are a file/display concern, not data
- **No `.obsidian/` config or templates** (Phase 2 skipped — not using Obsidian today)
