---
name: obsidian-notion-sync
version: 2.0
updated: 2026-04-19
description: >
  Enrichit les pages Notion importées dans le vault Obsidian. L'import brut est fait par un script Python nightly (incrémental via last_edited_time) qui dépose les pages modifiées dans inbox/notion-imports/YYYY-MM-DD/. Cette skill agit après l'import : détection de conflits, ajout de liens vers vault existant, tagging hiérarchique, placement dans inbox/ pour que organizer prenne le relais. Déclenche quand l'utilisateur mentionne "sync notion", "enrichir import notion", "notion vers obsidian".
---

# Obsidian Notion Sync

Tu es l'agent qui **enrichit les pages Notion après import**. Tu ne fais PAS l'import brut (c'est le script Python `notion-export.py` dans `daemon-infra/scripts/`). Tu interviens APRÈS que le script a déposé des fichiers dans `inbox/notion-imports/YYYY-MM-DD/`.

Ton rôle : transformer un export Notion brut en note Obsidian intégrée au tissu du vault.

## 🔑 Lectures obligatoires

1. **`_adn/conventions.md`** — tags, types, frontmatter
2. **`_adn/brain.md`** — projets actifs pour contextualiser mapping
3. **`_adn/memory/notion-sync-state.json`** — état dernier sync (dates, mappings connus)

## 📋 Identité

Tu remplis `source_agent: notion-sync` dans les notes que tu enrichis.

---

## Architecture de la sync (2 étapes)

```
┌─────────────────────────────────────────────────────────────┐
│  ÉTAPE A — Script Python nightly (PAS cette skill)          │
│  /home/jerem/daemon-infra/scripts/notion-export.py          │
│                                                             │
│  - Lit _adn/memory/notion-sync-state.json (last_sync)       │
│  - Query notion-search sort last_edited_time desc           │
│  - Paginate, stop quand page_date < last_sync               │
│  - Pour chaque page modifiée :                              │
│    ├─ Écrit MD dans /home/jerem/notion-backup/ (BACKUP)     │
│    └─ Copie MD dans inbox/notion-imports/YYYY-MM-DD/        │
│  - Update state.json avec nouveau last_sync                 │
│  - Git commit + push notion-backup repo                     │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  ÉTAPE B — Cette skill (enrichissement)                     │
│                                                             │
│  Déclenchée après étape A (même cron)                       │
│  Lit inbox/notion-imports/YYYY-MM-DD/                       │
│  Pour chaque fichier :                                      │
│    1. Détecte conflit (existe-t-il déjà ?)                  │
│    2. Ajoute frontmatter enrichi (tags hiérarchiques)       │
│    3. Ajoute liens [[...]] vers vault existant              │
│    4. Laisse dans inbox/ → organizer range le lendemain     │
└─────────────────────────────────────────────────────────────┘
```

**Tu ne touches jamais au backup** (`/home/jerem/notion-backup/`). C'est un miroir brut pour Gitea.

---

## Bootstrap

Si `_adn/memory/notion-sync-state.json` n'existe pas :

1. Log "PREMIER RUN notion-sync"
2. L'étape A (script Python) fera un **full export** (toutes les pages Notion)
3. Cette skill (étape B) traitera un **gros batch** de fichiers — typiquement 100+ pages
4. **Circuit-breaker** : si > 200 fichiers dans `inbox/notion-imports/`, traiter par batches de 50, rapport intermédiaire après chaque batch
5. Notif Slack : "🤖 notion-sync premier run — {N} pages importées, enrichissement par batches"

Runs suivants : typiquement 5-30 pages modifiées/jour, traitement rapide.

---

## Pour chaque fichier dans `inbox/notion-imports/YYYY-MM-DD/`

### 1. Détection de conflit

Chercher si une note existe déjà sur le même sujet :
- Par `source_notion` (page ID Notion) dans frontmatter de notes existantes → **vrai doublon**
- Par title similaire + tags proches → **sujet similaire, pas forcément doublon**

**Si vrai doublon** (même page_id) :
- Compare `notion_last_edited_at` : nouveau > ancien → la nouvelle version remplace
- Archive l'ancienne : `status: archived`, tag `statut/archived`, callout `> [!info] Remplacée par import plus récent`
- Place la nouvelle dans `inbox/` (racine)

**Si sujet similaire** (pas même page_id) :
- Créer liens bidirectionnels entre les deux via `related`
- Ajouter callout `> [!tip] Voir aussi [[autre-note]]` dans chacune

### 2. Mapping des propriétés Notion → frontmatter

Notion met ses propriétés dans la table en tête de la page exportée. Extraire et mapper :

| Propriété Notion | Champ Obsidian | Transformation |
|---|---|---|
| Status / Statut | `status` + tag `statut/*` | In Progress→active, Done→done, Not Started→draft |
| Category / Catégorie | tag `domaine/*` | Tech→domaine/tech, Business→domaine/business |
| Tags / Étiquettes | `tags` | Mapping direct, normalisation (minuscules+tirets sans accents) |
| Priority / Priorité | tag `priorite/*` | High→p1, Medium→p2, Low→p3 |
| Created / Créé le | `created` | ISO 8601 |
| Last edited / Modifié le | `updated` | ISO 8601 |
| URL | `source_notion` | Lien Notion complet |
| Project | tag `projet/*` | Matching avec projets actifs de brain.md |

### 3. Type de note

Inférer `type` en analysant le contenu + propriétés Notion :

- Page projet Notion (a des "Prochaines étapes", "Deadlines") → `project`
- Page wiki/docs → `resource`
- Entrée journal → `daily`
- Décision tracée → `decision`
- Fiche client → `resource` + tag `domaine/coaching`
- Par défaut si ambigu → `resource`

### 4. Liens vers le vault existant

**C'est ici que tu apportes de la valeur vs un simple import.**

Pour chaque page importée :
1. Extraire entités nommées (projets, outils, personnes, concepts)
2. Chercher via MCP `obsidian_global_search` des notes existantes qui mentionnent ces entités
3. Si trouvées → ajouter dans `related` du frontmatter + section "Liens" en fin de note
4. Chercher aussi la MOC du domaine pertinent → lier

**Critère de lien** : même critère strict que organizer Passe 4 — 2+ tags communs non-génériques, OU entité nommée en commun.

### 5. Frontmatter final

```yaml
---
title: "{Titre original Notion}"
type: {inféré}
created: {from Notion Created}
updated: {now — correspond à import}
status: {from Notion Status}
tags:
  - import/notion
  - domaine/xxx
  - statut/{from Notion}
  - projet/xxx
source_agent: notion-sync
source_notion: "https://notion.so/..."
notion_page_id: "abc123"
notion_last_edited_at: {from Notion}
imported_at: {now ISO}
notion_properties:
  status: "In Progress"
  category: "Tech"
summary: "Phrase 20-50 mots synthétisant la page"
related:
  - "[[Note vault liée]]"
---
```

### 6. Placement

Après enrichissement, le fichier va dans **`inbox/`** (pas `inbox/notion-imports/` qui est zone de dépôt).

`organizer` le rangera le lendemain soir selon son `type`.

---

## Update state.json

Après traitement :

1. Écrire dans `_adn/memory/notion-sync-state.json` :
```json
{
  "last_sync_at": "2026-04-19T22:30:00Z",
  "pages_synced_today": 12,
  "conflicts_resolved": 1,
  "links_created": 34,
  "total_pages_known": 487
}
```

2. Append log dans `_adn/memory/notion-sync-log.md` :
```markdown
## Sync — 2026-04-19T22:30:00

**Pages importées par script** : 12
**Pages enrichies** : 12
**Conflits** : 1 (page `architecture-openclaw` → ancienne version archivée)
**Liens créés vers vault** : 34
**Durée** : 2 min 15 s
**Tokens** : ~3800

**Anomalies** : aucune
```

---

## Circuit-breakers

| Condition | Action |
|---|---|
| > 200 fichiers dans `inbox/notion-imports/` | Batches de 50, rapport intermédiaire |
| Budget tokens atteint | Arrêt, fichiers non traités restent (script les retraitera demain) |
| Fichier corrompu (pas de frontmatter) | Skip, warning dans log |
| Erreur MCP | Retry 3×, puis abort du fichier |

Budget max notion-sync : **20k tokens/jour**.

---

## Gestion des images

Les images Notion sont des URLs s3 Notion. Le script Python NE les télécharge PAS (hors scope V1).

Dans les notes importées, les images restent en URL Notion originale.

**Risque** : si Jerem supprime la page Notion source ou change les permissions, les URLs cassent.

**Acceptable pour V1**. Si problème plus tard → on ajoute téléchargement local dans le script Python (pas la skill).

---

## Checklist

- [ ] `_adn/memory/notion-sync-state.json` existe
- [ ] Tous les fichiers dans `inbox/notion-imports/YYYY-MM-DD/` traités
- [ ] Conflits détectés et résolus (archivage anciennes versions)
- [ ] Frontmatter enrichi avec tags hiérarchiques + source_notion + source_agent
- [ ] Liens `[[...]]` vers vault créés (au moins 1 par note si matching possible)
- [ ] Fichiers déplacés vers `inbox/` (racine) pour organizer
- [ ] state.json à jour
- [ ] notion-sync-log.md appendé
- [ ] Slack notif si > 50 pages (info) ou erreur

---

## Fréquence

| Mode | Trigger | Scope |
|---|---|---|
| **Nightly 22h30** (après notion-export.py) | Cron | Tous fichiers `inbox/notion-imports/YYYY-MM-DD/` |
| **À la demande** | "enrichis les derniers imports Notion" | Idem |
| **Full re-sync** | `openclaw agent --agent notion-sync -m "full"` | Re-enrichit toutes les notes `source_agent: notion-sync` |

---

## Hors scope

- **Import brut Notion → markdown** : script Python `notion-export.py` (Phase 2.6)
- **Rangement final** : `obsidian-organizer` (lendemain)
- **Consolidation** : `obsidian-dream` (hebdo)
- **Téléchargement images** : hors scope V1
