---
description: Sync ~/.personal/tracker.json to/from an Obsidian markdown vault at ~/.personal/vault/. Bidirectional, idempotent, runs on demand or via 15-min launchd cron. Frontmatter on each note mirrors tracker fields; body holds prose. Triggered by 'sync vault', '/vault-sync', or auto on tracker.json changes.
---

# personal-vault-sync

Reconciles `~/.personal/tracker.json` (state) with an Obsidian vault at `~/.personal/vault/` (notes + Dataview surface). Both are local files; the sync is cheap and reversible.

## Why this skill exists

Tracker.json is great for skills (structured queries, JSON manipulation, programmatic access). Obsidian is great for humans (prose, linking, daily review, mobile via iCloud).

Want both. This skill keeps them in sync.

## Trigger phrases

- "Sync vault"
- "Update vault from tracker"
- "Update tracker from vault"
- `/vault-sync`
- Auto: launchd job every 15 min (optional, install via `~/.personal/scripts/install-vault-sync-cron.sh`)

## Vault structure

```
~/.personal/vault/
├── _Home.md                    ← Dataview-driven dashboard
├── Applications/
│   ├── JOB-00001-acme-sr-csm.md
│   └── ...
├── Interviews/
│   ├── INT-00001-acme-round-1.md
│   └── ...
├── Contacts/
│   ├── jane-doe-acme.md
│   └── ...
├── Projects/
│   ├── PROJECT-00001-personal-blog.md
│   └── ...
├── Daily/
│   ├── 2026-05-08.md
│   └── ...
├── Weekly/
│   ├── 2026-W19.md
│   └── ...
└── _meta/
    └── last-sync.json
```

## Frontmatter contract

Every note in `Applications/`, `Interviews/`, `Contacts/`, `Projects/` has YAML frontmatter mirroring the tracker.json schema. Body is freeform prose.

Example `JOB-00012-acme-sr-csm.md`:

```markdown
---
id: JOB-00012
type: application
company: Acme Corp
company-slug: acme
role: Senior CSM
url: https://acme.com/jobs/sr-csm
score: 4.2
status: onsite-scheduled
created: 2026-05-01
applied: 2026-05-03
last_action: 2026-05-08
next_action: prep round 3
next_action_due: 2026-05-12
linked_outreach: [OUTREACH-00034]
linked_interviews: [INT-00007, INT-00009]
linked_contacts: [[jane-doe-acme]]
tags: [job, csm, mid-market]
---

# Acme Corp — Senior CSM

## Why I applied

[Prose...]

## Stages

- [x] JD analyzed (score 4.2)
- [x] Resume tailored
- [x] Cover letter drafted
- [x] Submitted via Greenhouse
- [x] Recruiter screen — passed
- [x] Hiring manager round — passed
- [ ] Final round on 2026-05-12

## Notes

[Freeform prose for notes, observations, follow-ups]

## Linked

- Recruiter: [[jane-doe-acme]]
- Round 1 transcript: [[INT-00007-acme-round-1]]
```

## Sync logic

### tracker → vault (default direction)

For each item in `tracker.json`:

1. Resolve the target path: `Applications/` for JOB-*, `Interviews/` for INT-*, `Projects/` for PROJECT-*, `Contacts/` for CONTACT-*.
2. Filename: `{id}-{slug-from-title}.md`.
3. If file exists: replace frontmatter, preserve body content below `---`.
4. If file doesn't exist: create with frontmatter + scaffolded body sections.
5. Update `_meta/last-sync.json` with timestamp.

### vault → tracker

Walk vault notes:

1. Parse frontmatter (yaml).
2. For each note with valid frontmatter, look up in tracker.json by `id`.
3. If frontmatter newer than tracker (per `last_action` or explicit `_synced_at`): update tracker.
4. If new id seen in vault but not in tracker: add to tracker.

### Conflict resolution

- Tracker.json is source of truth for **status** and **dates**.
- Vault is source of truth for **prose body** and **linked notes (`[[wiki-links]]`)**.
- On conflict between frontmatter and tracker.json: **most recent `last_action` wins**, with the loser's value preserved in `_conflicts/` for review.

## Initial scaffolding

First run creates:

- `_Home.md` with Dataview queries (Today, Hot, Stale, Active applications, Recent interviews)
- All current tracker items as notes with scaffolded body sections
- 6 Templater templates in `_meta/templates/` for new applications, interviews, contacts, projects, daily, weekly

## `_Home.md` Dataview seeds

```markdown
# Personal Hub

## Today

\`\`\`dataview
TABLE WITHOUT ID file.link as Item, next_action as "Next", next_action_due as "Due"
FROM "Applications" or "Interviews" or "Projects"
WHERE next_action_due = date(today)
SORT next_action_due ASC
\`\`\`

## Hot (next 5 business days)

\`\`\`dataview
TABLE WITHOUT ID file.link as Item, status, next_action, next_action_due as "Due"
FROM "Applications"
WHERE next_action_due <= date(today) + dur(5 days) AND status != "rejected" AND status != "withdrew"
SORT next_action_due ASC
\`\`\`

## Stale (no action >7 days)

\`\`\`dataview
TABLE WITHOUT ID file.link as Item, last_action, status
FROM "Applications" or "Interviews"
WHERE last_action < date(today) - dur(7 days) AND status != "rejected"
SORT last_action ASC
\`\`\`

## Active projects

\`\`\`dataview
TABLE WITHOUT ID file.link as Project, status, next_action
FROM "Projects"
WHERE status != "complete" AND status != "shelved"
\`\`\`
```

## Output to user

```markdown
**Vault sync complete**

Direction: [tracker → vault | vault → tracker | bidirectional]
Notes updated: [N]
Notes created: [N]
Notes archived: [N] (status=done/rejected, moved to Archive/)
Conflicts: [N] (see _conflicts/ for details)

**Last sync:** YYYY-MM-DD HH:MM
**Next auto-sync:** [if cron installed] YYYY-MM-DD HH:MM
```

## Discipline

- Idempotent. Running it twice in a row produces no diffs.
- Atomic. Writes go via temp file + rename, so an interrupted sync doesn't corrupt the vault.
- Backup before destructive ops. `_meta/backups/` keeps last 7 sync states.

## Privacy

The vault contains personal notes, applications, salary expectations, recruiter contacts. NEVER commit `~/.personal/vault/` to a public repo. The `.gitignore` baseline covers it.

If using Obsidian Sync ($48/year) or iCloud sync, that's fine — it's your data going to your storage. Don't sync to anyone else's storage.
