252 lines
10 KiB
Markdown
252 lines
10 KiB
Markdown
---
|
||
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
|