diff --git a/.claude/hooks-examples/auto-dream.json b/.claude/hooks-examples/auto-dream.json new file mode 100644 index 0000000..3fe4ebc --- /dev/null +++ b/.claude/hooks-examples/auto-dream.json @@ -0,0 +1,51 @@ +{ + "_comment": "Auto Dream hook for ulk — copy the 'hooks' block into your ~/.claude/settings.json. Spec : framework/agents/_shared/auto-dream-protocol.md. Cycle de consolidation mémoire inspiré du sommeil REM : merge doublons, normalise horodatages, archive obsolètes, reconstruit l'index. Conditions par défaut : 24h écoulées ET ≥5 sessions depuis le dernier cycle. Le hook lance le trigger en arrière-plan, ne bloque jamais la session.", + + "_install": "Run ./install.sh --with-auto-dream to copy this file into your ~/.claude/hooks-examples/ folder + the trigger script into ~/.claude/hooks/. Then merge the hooks block manually into your ~/.claude/settings.json (this script does NOT modify your settings automatically — by design).", + + "_requires": "framework/tools/hooks/auto-dream.sh installed in ~/.claude/hooks/auto-dream.sh (handled by --with-auto-dream).", + + "hooks": { + "SessionStart": [ + { + "_comment": "On session start, increment the session counter and trigger a dream cycle in background if conditions are met (24h + 5 sessions). Pure shell, 0 token cost. The cycle itself runs in background via `claude --headless` and never blocks the user session.", + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "test -x ~/.claude/hooks/auto-dream.sh && ~/.claude/hooks/auto-dream.sh tick >/dev/null 2>&1 || true" + } + ] + } + ] + }, + + "_environment": { + "_comment": "Override default thresholds via these env vars in your shell profile or ~/.claude/settings.json (env section).", + "AUTO_DREAM_MIN_HOURS": 24, + "AUTO_DREAM_MIN_SESSIONS": 5 + }, + + "_commands": { + "_comment": "Manual control commands. The trigger script is also useful standalone.", + "status": "~/.claude/hooks/auto-dream.sh status", + "force_cycle": "~/.claude/hooks/auto-dream.sh force", + "reset_counter": "~/.claude/hooks/auto-dream.sh reset", + "natural_invocation": "Say 'consolidate my memory files' or 'lovecraft memory dream' inside any Claude Code session — same effect as the hook trigger but synchronous and visible." + }, + + "_alternative_strict_mode": { + "_comment": "Pour les utilisateurs qui veulent un cycle TRES fréquent (debug ou vault à fort turnover) : abaisse les seuils à 8h / 2 sessions. ⚠️ génère plus de churn git sur docs/_memory/.", + "SessionStart": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "AUTO_DREAM_MIN_HOURS=8 AUTO_DREAM_MIN_SESSIONS=2 ~/.claude/hooks/auto-dream.sh tick >/dev/null 2>&1 || true" + } + ] + } + ] + } +} diff --git a/.claude/rules/install-reference.md b/.claude/rules/install-reference.md index 503be26..8960cee 100644 --- a/.claude/rules/install-reference.md +++ b/.claude/rules/install-reference.md @@ -22,6 +22,7 @@ paths: ./install.sh --with-nothing-design # + skill Nothing design system (dominikmartn) ./install.sh --with-hue-skill # + skill Hue design language generator (dominikmartn) ./install.sh --with-memory-loop # + hooks memory loop +./install.sh --with-auto-dream # + hook Auto Dream consolidation REM-style (24h + 5 sessions → merge doublons, normalise horodatages, archive obsolètes, reconstruit MOC) — sandbox stricte, lock multi-instances, background ; voir _shared/auto-dream-protocol.md ./install.sh --with-xavier-hook # + hook SessionStart Xavier (carte contexte + mini-diff, 0 tokens Claude) ./install.sh --with-cli-telemetry # + hook CLI usage tracking → ./framework/tools/cli-report.sh ./install.sh --with-accountability # + audit trail mutations (Edit/Write/Bash) → .ulk-reports/accountability.jsonl diff --git a/CLAUDE.md b/CLAUDE.md index 1c86947..a315728 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -155,17 +155,48 @@ Référence canonique : `framework/agents/_shared/design-source-protocol.md` ### Knowledge Vault Loop (Obsidian-compatible) -Cross-session memory via Obsidian-compatible vault. Three commands on `lovecraft (47)`: +Cross-session memory via Obsidian-compatible vault. Four commands on `lovecraft (47)`: - **`lovecraft memory capture`** — `MEMORY.md` (project root) → `docs/_memory//` (delegates to obsidian-vault 39) - **`lovecraft memory distribute`** — `docs/_memory/` → `CLAUDE.md` block `...` (delegates to shuri 01 mode=sync) - **`lovecraft memory surface`** — read-only summary for godspeed/bruce/gandalf +- **`lovecraft memory dream`** — **(AUTO-DREAM)** REM-style consolidation cycle : merge doublons (≥ 0.85), normalise horodatages relatifs en dates absolues, archive obsolètes (> 90j + non-référencées), promeut patterns récurrents en règles, reconstruit `00-MOC.md`. Sandbox d'écriture stricte. Capture écrit, dream nettoie. Auto-integration: 2b3 (Phase 5.7) captures, godspeed (Phase 1.5) surfaces, gandalf (Phase 5) audits health. Opt-in hooks: `./install.sh --with-memory-loop` copies `.claude/hooks-examples/memory-loop.json` (manual merge into `~/.claude/settings.json` required). -Spec: `framework/agents/_shared/memory-protocol.md` +Spec: `framework/agents/_shared/memory-protocol.md` · Auto Dream : `framework/agents/_shared/auto-dream-protocol.md` + +### Auto Dream — Memory Consolidation (REM-style, depuis 2026-05-07) + +Cycle de consolidation mémoire inspiré du sommeil REM, complémentaire à la boucle capture/distribute/surface. Inspiré d'Anthropic *Auto Dream* (Claude Code v2.1.83+, feature flag) et du papier *Sleep-time Compute* (UC Berkeley). + +**Problème résolu** : sur la durée, `docs/_memory/` accumule du bruit, des contradictions, des doublons, des horodatages relatifs périmés, des index pourris. Sans nettoyage, Claude lit du contenu désuet et prend des décisions sur cette base. **Capture écrit, dream nettoie.** + +**Déclenchement automatique** : hook `SessionStart` qui incrémente un compteur de sessions et déclenche un cycle en arrière-plan (`& disown`) quand **24h écoulées ET ≥ 5 sessions** depuis le dernier dream. État dans `.claude/state/auto-dream.json`. Seuils configurables via `AUTO_DREAM_MIN_HOURS` / `AUTO_DREAM_MIN_SESSIONS`. + +**Déclenchement manuel** : `lovecraft memory dream` · `--force` (ignore conditions) · `--dry-run` (simulation) · alias naturel « consolidate my memory files ». + +**Garde-fous** : +- **Sandbox d'écriture stricte** — pendant un cycle, écriture autorisée uniquement dans `docs/_memory/**`, `.claude/memory-archive/**`, `.claude/state/auto-dream.json`, `.ulk-reports/auto-dream-*.json` et le bloc `` de `CLAUDE.md`. Jamais code source / tests / config / specs. Enforcement : `permissions: scoped-write` + post-flight diff qui flagge `🚨 SCOPE VIOLATION`. +- **Lock file** (`.claude/state/auto-dream.lock`) — `flock -n` ou `mkdir` atomique. Si tenu par une autre instance → exit silencieux. TTL 30 min pour dead-lock cleanup. +- **Background non-bloquant** — fork via `& disown`, ne ralentit jamais la session courante. +- **Aucune création** — strictement transformatif (merge / normalise / archive / rebuild). Pas de nouvelle entrée — c'est le rôle de `capture`. + +**Pipeline** (D1-D8) : scan → détection incohérences (doublons/contradictions/obsolètes/horodatages relatifs/wikilinks morts) → promotion patterns récurrents → reconstruction `00-MOC.md` → rapport machine `.ulk-reports/auto-dream-.json` → reset state + rapport humain. + +**Activation** : `./install.sh --with-auto-dream` (copie hook + script). Merge manuel du bloc `hooks` dans `~/.claude/settings.json` (settings jamais modifiés automatiquement — par design). + +**Pourquoi c'est bien pour les 85 agents du framework** : +1. **Le vault est partagé** — tous les agents lisent `docs/_memory/`. Le bruit s'amplifie en cascade ; un cycle dream améliore tous les futurs runs (multiplicateur d'effet). +2. **`distribute` injecte dans `CLAUDE.md`** — chaque entrée périmée/contradictoire devient un faux signal pour Claude à chaque session. Dream coupe la chaîne d'erreur à la source. +3. **Complémentaire à `curate`** — `curate` détecte (manuel, lecture seule). `dream` corrige (auto, écrit avec sandbox). Les deux coexistent. +4. **Invisible quand ça marche** — exécution background, 0 token côté hook (pur shell), 0 friction utilisateur. Le coût LLM n'apparaît que quand un cycle est effectivement lancé (~1× / semaine pour un projet actif). +5. **Indépendant du feature flag Anthropic** — la spec Anthropic est server-gated. ulk implémente côté projet, sur le vault projet, sans dépendance externe. +6. **Discipline forcée par sandbox** — la sandbox `permissions: scoped-write` empêche un cycle dream qui dérive de toucher du code. Robuste face à un futur LLM moins prudent. + +Spec : `framework/agents/_shared/auto-dream-protocol.md` · Script : `framework/tools/hooks/auto-dream.sh` · Hook : `.claude/hooks-examples/auto-dream.json` ## Path-Specific Rules (`.claude/rules/`) diff --git a/framework/agents/_shared/auto-dream-protocol.md b/framework/agents/_shared/auto-dream-protocol.md new file mode 100644 index 0000000..0d23112 --- /dev/null +++ b/framework/agents/_shared/auto-dream-protocol.md @@ -0,0 +1,251 @@ +# Auto Dream Protocol — Memory Consolidation Cycle + +> Cycle de **consolidation mémoire** inspiré du sommeil REM humain. +> Étape 4 de la boucle mémoire ulk : `capture` → `distribute` → `surface` → **`dream`**. +> Source : Anthropic *Auto Dream* (Claude Code v2.1.83+) · base académique « Sleep-time Compute » (UC Berkeley). +> +> Lu par : `47-lovecraft`, `00-godspeed`, `34-gandalf`, `08-2b3`, `framework/tools/hooks/auto-dream.sh`. + +--- + +## 1. Pourquoi + +Le vault `docs/_memory/` accumule au fil des sessions : +- **du bruit** — détails de session qui n'ont jamais resservi +- **des contradictions** — un pattern adopté il y a 2 mois, abandonné depuis sans archivage +- **des doublons** — similarité > 0.85 que `curate` a signalés mais que personne n'a fusionnés +- **des horodatages relatifs périmés** — « il y a 3 jours » dans une entrée vieille de 4 mois +- **des index pourris** — `00-MOC.md` qui ne reflète plus la structure réelle + +Sans nettoyage, ces incohérences finissent par **perturber le modèle** : Claude lit du contenu désuet ou contradictoire et prend des décisions sur cette base. + +**`capture` écrit. `dream` nettoie.** Les deux étapes sont complémentaires. + +--- + +## 2. Position dans la boucle + +``` +Session active + │ + ├── Claude écrit → MEMORY.md + │ + └── Fin de session + │ + ▼ + lovecraft memory capture ← écriture (étape 1) + │ + ▼ + lovecraft memory distribute ← projection vers CLAUDE.md (étape 2) + │ + ▼ + Session suivante + │ + ├── godspeed Phase 1.5 + │ └── lovecraft memory surface ← lecture (étape 3) + │ + └── (en arrière-plan, conditions remplies) + │ + ▼ + lovecraft memory dream ← consolidation (étape 4) + │ + └── nettoie, fusionne, normalise, rebâtit l'index +``` + +`dream` ne crée jamais de nouvelles entrées. Il **transforme l'existant** : merge, archive, normalise, réécrit l'index. + +--- + +## 3. Conditions de déclenchement + +Par défaut (alignées sur la spec Anthropic) : + +| Condition | Valeur défaut | Variable d'env | +|-----------|---------------|----------------| +| Temps écoulé depuis le dernier dream | ≥ 24 h | `AUTO_DREAM_MIN_HOURS` | +| Sessions écoulées depuis le dernier dream | ≥ 5 | `AUTO_DREAM_MIN_SESSIONS` | + +**Les deux conditions doivent être vraies.** Si l'une manque → skip silencieux. + +Override manuel : `lovecraft memory dream --force` ignore les conditions (utile pour tester ou forcer un nettoyage avant un audit). + +Le compteur de sessions est incrémenté par le hook `SessionStart` (voir §6). Il est remis à zéro à la fin de chaque cycle dream. + +--- + +## 4. Garde-fous (non négociables) + +### 4.1 Sandbox d'écriture + +Pendant un cycle dream, l'agent ne peut écrire **que dans** : + +- `docs/_memory/**` — vault de mémoire (cible principale) +- `docs/_memory/00-MOC.md` — index reconstruit +- `docs/_memory/archive/**` — entrées obsolètes déplacées +- `.claude/memory-archive/**` — archives MEMORY.md historiques (purge éventuelle) +- `.ulk-reports/auto-dream-.json` — rapport de cycle + +**Interdit** : code source, configuration, tests, `CLAUDE.md` (sauf bloc ``), specs, fichiers projet hors `docs/_memory/`. + +L'enforcement est triple : +1. **Frontmatter `permissions: scoped-write` + `permissions_scope:`** sur l'invocation lovecraft mode=dream +2. **Pré-flight script** dans `framework/tools/hooks/auto-dream.sh` qui vérifie la liste avant de lancer Claude +3. **Post-flight diff** : si le cycle a touché un fichier hors scope, le rapport flagge `🚨 SCOPE VIOLATION` et un revert manuel est suggéré + +### 4.2 Lock file (concurrence multi-instances) + +`.claude/state/auto-dream.lock` créé via `flock -n` avant tout cycle. Si déjà détenu par une autre instance Claude Code ouverte sur le même projet → exit 0 silencieux. + +Mécanisme : `flock` Linux/macOS · fallback `mkdir` atomique si `flock` indisponible. PID + timestamp écrits dans le fichier pour debug. Lock libéré explicitement à la fin OU expiré après 30 minutes (TTL mort). + +### 4.3 Exécution non-bloquante + +Le hook `SessionStart` ne lance jamais le cycle synchronous. Il fork un processus en arrière-plan (`& disown`) et rend la main immédiatement. La session courante n'est jamais ralentie. Si le cycle plante, la session continue normalement. + +### 4.4 Aucune création d'entrée + +`dream` est strictement transformatif. Il ne fabrique pas de nouvelle leçon, pattern, mistake. Il ne déduit rien à partir de la transcription de session courante (c'est le rôle de `capture`). + +--- + +## 5. Pipeline du cycle + +### Phase D1 — Scan +```bash +find docs/_memory/ -name "*.md" -not -path "*/archive/*" +``` +Pour chaque fichier : parser frontmatter (`title`, `category`, `date`, `tags`, `severity`, `status`). + +### Phase D2 — Détection des incohérences + +| Type | Détection | Action | +|------|-----------|--------| +| **Doublon** | similarité titre normalisé ≥ 0.85 dans la même catégorie | merge (ajoute contenu en bas, garde le plus ancien comme canonique, archive l'autre) | +| **Contradiction** | deux entrées même catégorie/sujet avec verdict opposé (`use X` vs `avoid X`) | flag dans rapport, pas de merge auto, status `contradicts: ` ajouté aux deux | +| **Obsolète** | `date > 90 j` ET `severity != critical` ET pas référencée par CLAUDE.md depuis 30 j | `status: archived` + déplace vers `docs/_memory/archive/` | +| **Horodatage relatif** | regex `\b(hier|il y a \d+ (jour|semaine|mois)|recently|last week)\b` | remplace par date absolue calculée depuis `frontmatter.date` | +| **Wikilink mort** | `[[...]]` pointant vers fichier inexistant | flag dans rapport (pas de fix auto) | + +### Phase D3 — Promotion + +Si un `pattern` a été référencé ≥ 3 fois dans `CLAUDE.md` distribute sur une fenêtre de 60 jours → promotion en `rule` avec `enforcement: advisory`. + +### Phase D4 — Reconstruction de l'index + +Régénère `docs/_memory/00-MOC.md` : +- Liste catégorisée par `category` +- Top 10 entrées par `severity: critical` puis recency +- Compteurs par catégorie +- Date de dernière consolidation + +### Phase D5 — Rapport + +Écrit `.ulk-reports/auto-dream-.json` : + +```json +{ + "ts": "2026-05-07T03:14:00Z", + "duration_ms": 1842, + "trigger": "auto" | "manual" | "force", + "scanned": 47, + "merged": 3, + "archived": 5, + "promoted": 1, + "contradictions_flagged": 2, + "wikilinks_dead": 4, + "timestamps_normalized": 12, + "moc_rebuilt": true, + "scope_violations": [], + "files_touched": ["docs/_memory/00-MOC.md", "docs/_memory/03-lessons/...", "..."] +} +``` + +### Phase D6 — Mise à jour state + +Écrit `.claude/state/auto-dream.json` : +```json +{ + "last_run_at": "2026-05-07T03:14:00Z", + "sessions_since_last_dream": 0, + "total_dreams": 7, + "first_session_at": "2026-04-12T08:30:00Z" +} +``` + +--- + +## 6. Hook `SessionStart` + +Installé via `./install.sh --with-auto-dream`. Recette dans `.claude/hooks-examples/auto-dream.json` (l'utilisateur merge manuellement dans `~/.claude/settings.json` — pas de modification automatique des settings). + +Le hook lance `framework/tools/hooks/auto-dream.sh tick` qui : + +1. Incrémente `sessions_since_last_dream` dans `.claude/state/auto-dream.json` +2. Vérifie les conditions (24 h + 5 sessions) +3. Si remplies → fork en arrière-plan : `claude --headless --no-interactive 'lovecraft memory dream' &` +4. Sinon → exit 0 silencieux + +Le coût hook côté tokens : **0** (le check est pur shell, sans LLM). + +--- + +## 7. Invocation manuelle + +Sans hook, l'utilisateur peut déclencher un cycle : + +```bash +# Via Claude Code +"consolidate my memory files" # alias naturel +"lovecraft memory dream" # commande directe +"lovecraft memory dream --force" # ignore les conditions +"lovecraft memory dream --dry-run" # simulation, n'écrit rien +``` + +Sans la commande `/dream` officielle d'Anthropic (statut feature flag), c'est l'invocation textuelle qui pilote le cycle dans ulk. + +--- + +## 8. Intégration agents + +| Agent | Phase | Action | +|-------|-------|--------| +| `47-lovecraft` | mode `dream` | Pipeline D1-D6 ci-dessus | +| `00-godspeed` | Phase 1.5 (surface) | Si `last_run_at` > 7 jours → flag « vault non consolidé » dans le diagnostic | +| `34-gandalf` | Phase 5 (vault health) | Lit `.claude/state/auto-dream.json` ; alerte si conditions remplies depuis > 48 h sans cycle (= hook désactivé ou cassé) | +| `08-2b3` | Phase 5.7 | Inchangé. Capture seule. Le checkpoint ne déclenche pas dream (séparation des responsabilités). | + +--- + +## 9. Différence avec `curate` + +`curate` et `dream` partagent une partie de leur logique mais diffèrent sur trois axes : + +| Axe | `curate` | `dream` | +|-----|----------|---------| +| **Déclenchement** | Manuel uniquement | Automatique (hook) ou manuel | +| **Périmètre** | Scoring + détection doublons → liste pour humain | Pipeline complet, exécute les corrections (merge, normalise, rebuild MOC) | +| **Granularité** | Une catégorie à la fois (`--threshold-days N`) | Vault entier en un cycle | +| **Output** | Rapport humain (review nécessaire) | Vault corrigé + rapport machine (`.ulk-reports/auto-dream-*.json`) | + +`curate` reste utile pour des nettoyages ciblés. `dream` est pensé pour tourner sans supervision. + +--- + +## 10. Statut et déploiement + +- **Spec ulk** : actif depuis 2026-05-07 (cette implémentation). +- **Spec Anthropic** : feature flag côté serveur, déploiement progressif. Tous les utilisateurs Claude Code n'ont pas accès au toggle `/memory` *Auto-dream: on*. +- **Indépendance** : ulk implémente Auto Dream **côté projet** (vault `docs/_memory/`, hook local, état dans `.claude/state/`). Aucune dépendance au feature flag Anthropic. Le jour où Claude Code déploie nativement, les deux mécanismes coexistent (le notre opère sur le vault projet ulk, le natif sur la mémoire utilisateur Anthropic). + +--- + +## 11. Références + +- `_shared/memory-protocol.md` — boucle mémoire (capture/distribute/surface/curate) +- `47-lovecraft.md` — orchestrateur, mode `dream` +- `framework/tools/hooks/auto-dream.sh` — trigger script +- `.claude/hooks-examples/auto-dream.json` — recette hook +- Anthropic feature : tessl.io · claudefa.st · zenvanriel.com · antoniocortes.com · wmedia.es +- Académique : « Sleep-time Compute », UC Berkeley + +> Protocole défini le 2026-05-07. ULK-AUTODREAM-001. diff --git a/framework/agents/_shared/memory-protocol.md b/framework/agents/_shared/memory-protocol.md index f962546..6d55e70 100644 --- a/framework/agents/_shared/memory-protocol.md +++ b/framework/agents/_shared/memory-protocol.md @@ -36,6 +36,9 @@ Session active **Principe** : MEMORY.md est un **staging file** transient. Le vault Obsidian est la **source de vérité persistante**. CLAUDE.md est la **surface de distribution** vers les nouvelles sessions. +> **Cycle de consolidation REM-style** : voir `_shared/auto-dream-protocol.md`. **Capture écrit, dream nettoie.** +> Auto Dream est l'étape 4 de la boucle : elle merge les doublons, normalise les horodatages relatifs, archive les obsolètes et reconstruit `00-MOC.md`. Déclenchement automatique (hook) quand 24h écoulées + 5 sessions, ou manuel via `lovecraft memory dream` ou « consolidate my memory files ». + --- ## 1. Format `MEMORY.md` (staging projet) @@ -263,10 +266,11 @@ Lovecraft `memory distribute` injecte un bloc dans `CLAUDE.md` projet, **toujour ### `47-lovecraft` (orchestrateur) -Mode `memory` avec 4 sous-commandes : +Mode `memory` avec sous-commandes : - `lovecraft memory capture` → lit MEMORY.md, délègue à `obsidian-vault (39)`, archive MEMORY.md - `lovecraft memory distribute` → lit `docs/_memory/`, filtre, délègue à `shuri (01) mode=sync` - `lovecraft memory surface` → lit `docs/_memory/`, retourne un résumé court (10-20 entrées) pour Godspeed/Bruce +- `lovecraft memory dream [--force] [--dry-run]` → **(AUTO-DREAM)** consolidation REM-style : merge doublons (similarité ≥ 0.85), normalise horodatages relatifs en dates absolues, archive obsolètes (`> 90j` + non-référencées), promeut patterns récurrents en règles, reconstruit `00-MOC.md`. Conditions auto : 24h + 5 sessions (`.claude/state/auto-dream.json`). Sandbox d'écriture stricte : `docs/_memory/**` + bloc CLAUDE.md vault uniquement. Lock file pour concurrence multi-instances. Voir `_shared/auto-dream-protocol.md`. - `lovecraft memory curate [--dry-run] [--threshold-days N]` → scan vault, scoring pertinence, déduplication (similarité ≥ 0.85), archivage entrées obsolètes (score ≤ 0 + date > N jours, défaut 90) vers `docs/_memory/archive/` ; `--dry-run` par défaut (simulation sans écriture) - `lovecraft memory audit [--since YYYY-MM-DD] [--verbose]` → liste les archives `.claude/memory-archive/MEMORY-*.md` avec méta-data (date, nb entrées `###`, catégories `##`) ; tableau Markdown + résumé statistique ; `--since` filtre par date ; `--verbose` ajoute les titres des entrées - `lovecraft memory redact [--dry-run] [--reason "..."]` → purge le contenu d'une archive (`archive:YYYY-MM-DD`) ou d'une entrée vault (`vault:`) pour PII/secret leak ; backup dans `.claude/memory-archive/redacted/` avant modification ; log dans `.ulk-reports/redact-log.jsonl` ; `--dry-run` par défaut (sécurité) @@ -292,6 +296,7 @@ Nouvelle Phase 5 (vault health) : - Si `MEMORY.md` existe et > 100 lignes → alerte "MEMORY.md déborde, lance `lovecraft memory capture`" - Si `docs/_memory/` existe et dernière modif > 30 jours → alerte "vault potentiellement obsolète" - Si bloc CLAUDE.md `` absent alors que vault existe → alerte "distribute jamais lancé" +- Si `.claude/state/auto-dream.json#last_run_at` > 7 jours alors que conditions remplies → alerte "Auto Dream désactivé ou cassé, lance `~/.claude/hooks/auto-dream.sh status`" --- diff --git a/framework/agents/orchestrators/47-lovecraft.md b/framework/agents/orchestrators/47-lovecraft.md index 88908ca..99e1fd7 100644 --- a/framework/agents/orchestrators/47-lovecraft.md +++ b/framework/agents/orchestrators/47-lovecraft.md @@ -3,7 +3,7 @@ name: lovecraft type: custom-command description: | Super agent documentation Obsidian — orchestre l'analyse, la reconstruction et la maintenance de toute la documentation centrée sur Obsidian comme hub. - Modes : full (pipeline complet), audit (analyse + remise en état), sync (mise à jour vault), init (nouveau projet), harmonize (détection d'état + migration + complétion intelligente), requirements (liste outils nécessaires), memory (boucle mémoire automatique : capture/distribute/surface/curate/audit/redact/sync/pull). + Modes : full (pipeline complet), audit (analyse + remise en état), sync (mise à jour vault), init (nouveau projet), harmonize (détection d'état + migration + complétion intelligente), requirements (liste outils nécessaires), memory (boucle mémoire automatique : capture/distribute/surface/dream/curate/audit/redact/sync/pull). Coordonne shuri (01), strange (16), friday (09), obsidian-vault (39). Invoquer quand on démarre un projet Obsidian-first, quand la doc est incomplète ou désorganisée, pour migrer une doc legacy vers la nouvelle structure, pour maintenir le vault à jour, ou pour gérer la boucle de mémoire automatique entre sessions. tools: Read, Glob, Grep, Bash, Write, MultiEdit, Task, AskUserQuestionTool @@ -17,6 +17,18 @@ extends: - _shared/context-protocol.md - _shared/obsidian-doc-protocol.md - _shared/cli-tools-protocol.md + - _shared/memory-protocol.md + - _shared/auto-dream-protocol.md +permissions: scoped-write +permissions_scope: + - "docs/_memory/**" + - "docs/_memory/00-MOC.md" + - "docs/_memory/archive/**" + - ".claude/memory-archive/**" + - ".claude/state/auto-dream.json" + - ".ulk-reports/auto-dream-*.json" + - "MEMORY.md" + - "CLAUDE.md" managed-agents: model: claude-sonnet-4-6 environment: ulk-dev @@ -44,7 +56,7 @@ Tu es l'orchestrateur central de la documentation. Ton rôle : analyser, reconst | **init** | Nouveau projet : génère tout depuis zéro + setup vault | "Nouveau projet doc", "Créer vault" | | **harmonize** | Détecte l'état (EMPTY/PARTIAL/DRIFTED/HEALTHY), construit plan de migration, demande confirmation, applique avec backup | "harmonize", "ranger doc", "migrer doc" | | **requirements** | Liste tous les outils nécessaires (plugins, MCPs, skills, CLIs) | "Requirements Obsidian", "Liste plugins" | -| **memory** | Boucle mémoire : `capture` (MEMORY.md → vault), `distribute` (vault → CLAUDE.md), `surface` (résumé), `curate` (nettoyage vault : scoring, dédup, archivage), `audit` (historique captures archives), `redact` (purge PII/secret dans archive ou vault entry), `sync` (push docs/_memory/ → memory_store workspace), `pull` (tire memory_store → docs/_memory/ local) | "lovecraft memory [capture|distribute|surface|curate|audit|redact|sync|pull]" | +| **memory** | Boucle mémoire : `capture` (MEMORY.md → vault), `distribute` (vault → CLAUDE.md), `surface` (résumé), **`dream`** (consolidation REM-style : merge doublons, normalise horodatages, archive obsolètes, reconstruit MOC), `curate` (nettoyage vault : scoring, dédup, archivage), `audit` (historique captures archives), `redact` (purge PII/secret dans archive ou vault entry), `sync` (push docs/_memory/ → memory_store workspace), `pull` (tire memory_store → docs/_memory/ local) | "lovecraft memory [capture\|distribute\|surface\|dream\|curate\|audit\|redact\|sync\|pull]" \| "consolidate my memory files" | > **Mode harmonize — workflow complet 8 phases** : `agents/_shared/vault-harmonize.md` > **Mode memory — capture/distribute/surface/curate** : `agents/_shared/vault-memory.md` @@ -245,6 +257,7 @@ lovecraft (47) — Orchestrateur central | `lovecraft memory capture` | MEMORY.md → docs/_memory/ | | `lovecraft memory distribute` | docs/_memory/ → CLAUDE.md | | `lovecraft memory surface` | Résumé vault (lecture seule) | +| `lovecraft memory dream [--force] [--dry-run]` | **Consolidation REM-style** — merge doublons, normalise horodatages relatifs, archive obsolètes, promeut patterns récurrents en règles, reconstruit `00-MOC.md`. Voir `_shared/auto-dream-protocol.md` | | `lovecraft memory curate [--dry-run] [--threshold-days N]` | Nettoyage vault : scoring pertinence, déduplication, archivage entrées obsolètes | | `lovecraft memory audit [--since YYYY-MM-DD] [--verbose]` | Historique des captures — liste archives `.claude/memory-archive/` avec méta-data | | `lovecraft memory redact [--dry-run] [--reason "..."]` | Purge PII/secret — `archive:YYYY-MM-DD` ou `vault:` ; `--dry-run` par défaut | @@ -254,6 +267,126 @@ lovecraft (47) — Orchestrateur central --- +## Mode memory dream + +> Invocation : `lovecraft memory dream [--force] [--dry-run]` +> Alias naturel : "consolidate my memory files" · "dream the vault" · "REM cycle" +> Spec complète : `_shared/auto-dream-protocol.md` + +Cycle de consolidation inspiré du sommeil REM. **Capture écrit, dream nettoie.** Pas de création de nouvelle entrée — uniquement transformation de l'existant. + +### Conditions de déclenchement (auto) + +Quand invoqué sans `--force`, refuser si : +- moins de 24 h depuis le dernier cycle (`.claude/state/auto-dream.json#last_run_at`) +- moins de 5 sessions écoulées depuis le dernier cycle (`sessions_since_last_dream`) + +Override : `--force` ignore les conditions. Test : `--dry-run` affiche le plan sans écrire. + +### Sandbox d'écriture (non négociable) + +Pendant ce mode, l'agent ne peut écrire que dans la liste `permissions_scope:` du frontmatter. Toute tentative d'écrire ailleurs (code source, tests, configs, specs) → **refus immédiat** + flag `🚨 SCOPE VIOLATION` dans le rapport. + +### Phase D1 — Acquisition du lock + +```bash +# Le hook script gère normalement le lock. En invocation manuelle : +./framework/tools/hooks/auto-dream.sh status +# Si "🔒 held" → exit ; un autre cycle tourne ailleurs +``` + +### Phase D2 — Scan vault + +```bash +find docs/_memory/ -name "*.md" -not -path "*/archive/*" | sort +``` + +Pour chaque fichier : parser frontmatter (`title`, `category`, `date`, `tags`, `severity`, `status`). + +### Phase D3 — Détection des incohérences + +| Type | Détection | Action automatique | +|------|-----------|-------------------| +| **Doublon** | similarité titre normalisé ≥ 0.85, même catégorie | merge (contenu fusionné en bas du plus ancien, l'autre archivé avec `superseded_by:`) | +| **Contradiction** | deux entrées sujet identique avec verdict opposé | **flag uniquement** (status `contradicts: `), pas de merge — review humaine requise | +| **Obsolète** | `date > 90j` ET `severity ≠ critical` ET non référencée par CLAUDE.md depuis 30j | `status: archived` + déplace vers `docs/_memory/archive/` | +| **Horodatage relatif** | regex `(hier\|il y a \d+ (jour\|semaine\|mois)\|recently\|last week\|yesterday)` | remplace par date absolue calculée depuis `frontmatter.date` | +| **Wikilink mort** | `[[...]]` pointant vers fichier inexistant | flag dans le rapport (pas de fix auto — peut être intentionnel) | + +### Phase D4 — Promotion + +Pattern référencé ≥ 3 fois dans `CLAUDE.md` distribute sur fenêtre 60 jours → promu en `rule` avec `enforcement: advisory` (déplacé de `04-patterns/` vers `01-rules/`). + +### Phase D5 — Reconstruction de `00-MOC.md` + +Régénérer entièrement l'index : +- Catégories triées (rules, lessons, patterns, mistakes, insights, research) +- Top 10 par `severity: critical` puis recency +- Compteurs par catégorie +- Date du cycle dream qui vient de le régénérer +- Format Obsidian (`obsidian-markdown` skill) + +### Phase D6 — Rapport JSON + +Écrire `.ulk-reports/auto-dream-.json` : + +```json +{ + "ts": "2026-05-07T03:14:00Z", + "duration_ms": 1842, + "trigger": "auto", + "scanned": 47, + "merged": 3, + "archived": 5, + "promoted": 1, + "contradictions_flagged": 2, + "wikilinks_dead": 4, + "timestamps_normalized": 12, + "moc_rebuilt": true, + "scope_violations": [], + "files_touched": ["docs/_memory/00-MOC.md", "docs/_memory/03-lessons/..."] +} +``` + +### Phase D7 — Mise à jour state + +```bash +./framework/tools/hooks/auto-dream.sh reset +``` + +Réinitialise `sessions_since_last_dream: 0`, met à jour `last_run_at`, incrémente `total_dreams`. + +### Phase D8 — Rapport humain + +``` +╔══════════════════════════════════════════════╗ +║ AUTO DREAM — lovecraft (REM cycle) ║ +╚══════════════════════════════════════════════╝ +🌙 Cycle #[N] — durée [X]ms +📋 Scannées : [N] entrées +🔁 Doublons fusionnés : [N] +🗂️ Obsolètes archivées : [N] +🎯 Patterns promus : [N] → règles +⚠️ Contradictions : [N] (flag seulement, review requise) +🔗 Wikilinks morts : [N] +🕒 Horodatages corrigés : [N] +📊 MOC reconstruit : ✅ +📄 Rapport : .ulk-reports/auto-dream-[ts].json +``` + +### Différence avec `curate` + +| Axe | `curate` | `dream` | +|-----|----------|---------| +| Déclenchement | Manuel | Hook auto (24h + 5 sessions) ou manuel | +| Périmètre | Scoring + détection (lecture seule par défaut) | Pipeline complet, écrit | +| Granularité | Une catégorie / `--threshold-days` | Vault entier | +| Output | Rapport humain pour review | Vault corrigé + rapport machine | + +`curate` est l'outil de revue ciblé. `dream` est le cycle non-supervisé. + +--- + ## Mode memory curate > Invocation : `lovecraft memory curate [--dry-run] [--threshold-days N]` diff --git a/framework/agents/registry.json b/framework/agents/registry.json index a1ac3e1..a3a38cc 100644 --- a/framework/agents/registry.json +++ b/framework/agents/registry.json @@ -76,7 +76,7 @@ "category": "orchestrators", "model": "opus", "phase": "orchestrator", - "description": "Super agent documentation Obsidian — orchestre l'analyse, la reconstruction et la maintenance de toute la documentation centrée sur Obsidian comme hub. Modes : full (pipeline complet), audit (analyse + remise en état), sync (mise à jour vault), init (nouveau projet), harmonize (détection d'état + migration + complétion intelligente), requirements (liste outils nécessaires), memory (boucle mémoire automatique : capture/distribute/surface/curate/audit/redact/sync/pull). Coordonne shuri (01), strange (16), friday (09), obsidian-vault (39). Invoquer quand on démarre un projet Obsidian-first, quand la doc est incomplète ou désorganisée, pour migrer une doc legacy vers la nouvelle structure, pour maintenir le vault à jour, ou pour gérer la boucle de mémoire automatique entre sessions.", + "description": "Super agent documentation Obsidian — orchestre l'analyse, la reconstruction et la maintenance de toute la documentation centrée sur Obsidian comme hub. Modes : full (pipeline complet), audit (analyse + remise en état), sync (mise à jour vault), init (nouveau projet), harmonize (détection d'état + migration + complétion intelligente), requirements (liste outils nécessaires), memory (boucle mémoire automatique : capture/distribute/surface/dream/curate/audit/redact/sync/pull). Coordonne shuri (01), strange (16), friday (09), obsidian-vault (39). Invoquer quand on démarre un projet Obsidian-first, quand la doc est incomplète ou désorganisée, pour migrer une doc legacy vers la nouvelle structure, pour maintenir le vault à jour, ou pour gérer la boucle de mémoire automatique entre sessions.", "tools": [ "Read", "Glob", diff --git a/framework/cli/internal/installer/catalog.go b/framework/cli/internal/installer/catalog.go index a758ae7..394a36a 100644 --- a/framework/cli/internal/installer/catalog.go +++ b/framework/cli/internal/installer/catalog.go @@ -90,6 +90,11 @@ var All = []Installable{ base: base{key: "memory-loop", flag: "--with-memory-loop", label: "memory-loop", description: "hooks mémoire inter-sessions (godspeed · 2b3 · lovecraft)", category: "hooks"}, configSrc: "memory-loop.json", }, + &HookModule{ + base: base{key: "auto-dream", flag: "--with-auto-dream", label: "auto-dream", description: "cycle de consolidation mémoire REM-style (24h + 5 sessions → merge doublons, normalise horodatages, archive obsolètes, reconstruit MOC) — voir _shared/auto-dream-protocol.md", category: "hooks"}, + scriptSrc: "auto-dream.sh", + configSrc: "auto-dream.json", + }, &HookModule{ base: base{key: "xavier-hook", flag: "--with-xavier-hook", label: "xavier-hook", description: "vérification contexte SessionStart, 0 tokens Claude", category: "hooks"}, configSrc: "xavier-session-check.json", diff --git a/framework/tools/hooks/auto-dream.sh b/framework/tools/hooks/auto-dream.sh new file mode 100755 index 0000000..97e8779 --- /dev/null +++ b/framework/tools/hooks/auto-dream.sh @@ -0,0 +1,274 @@ +#!/usr/bin/env bash +# auto-dream.sh — trigger script for ulk Auto Dream memory consolidation cycle +# +# Spec : framework/agents/_shared/auto-dream-protocol.md +# Hook : .claude/hooks-examples/auto-dream.json (SessionStart) +# +# Subcommands : +# tick incrémente compteur sessions ; déclenche un cycle si conditions remplies +# status affiche l'état courant (last_run_at, sessions, conditions) +# force lance un cycle immédiat (ignore les conditions) +# reset remet le compteur à zéro (utile après un dream manuel) +# +# Conditions par défaut (alignées sur la spec Anthropic) : +# AUTO_DREAM_MIN_HOURS=24 (≥ 24h depuis le dernier cycle) +# AUTO_DREAM_MIN_SESSIONS=5 (≥ 5 sessions depuis le dernier cycle) +# Les deux conditions doivent être vraies. +# +# Garde-fous : +# - lock file `.claude/state/auto-dream.lock` (concurrence multi-instances) +# - exécution background (& disown) — ne bloque jamais la session +# - sandbox d'écriture enforced côté agent (frontmatter permissions: scoped-write) + +set -euo pipefail + +# ----- Paths ----- +PROJECT_ROOT="$(pwd)" +STATE_DIR="${PROJECT_ROOT}/.claude/state" +STATE_FILE="${STATE_DIR}/auto-dream.json" +LOCK_FILE="${STATE_DIR}/auto-dream.lock" +LOG_FILE="${PROJECT_ROOT}/.ulk-reports/auto-dream-trigger.log" + +# ----- Config ----- +MIN_HOURS="${AUTO_DREAM_MIN_HOURS:-24}" +MIN_SESSIONS="${AUTO_DREAM_MIN_SESSIONS:-5}" +LOCK_TTL_MIN=30 # forced-release after this many minutes (dead lock cleanup) + +mkdir -p "${STATE_DIR}" "${PROJECT_ROOT}/.ulk-reports" + +# ----- Helpers ----- +now_iso() { date -u +%Y-%m-%dT%H:%M:%SZ; } +now_epoch() { date -u +%s; } + +log() { + printf '%s [auto-dream] %s\n' "$(now_iso)" "$*" >> "${LOG_FILE}" 2>/dev/null || true +} + +# Read JSON state file (auto-init if missing). Stdout = full JSON. +read_state() { + if [ ! -f "${STATE_FILE}" ]; then + cat < "${STATE_FILE}.tmp" + mv "${STATE_FILE}.tmp" "${STATE_FILE}" +} + +json_get() { + local key="$1" json="$2" + if command -v jq >/dev/null 2>&1; then + printf '%s' "${json}" | jq -r ".${key} // empty" + else + # Fallback regex parse (best-effort, expects flat JSON we control) + printf '%s' "${json}" | sed -n "s/.*\"${key}\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p; s/.*\"${key}\"[[:space:]]*:[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p" | head -1 + fi +} + +iso_to_epoch() { + local iso="$1" + [ -z "${iso}" ] || [ "${iso}" = "null" ] && { echo "0"; return; } + # GNU date (Linux) and BSD date (macOS) handle ISO 8601 with -d / -j -f + if date --version >/dev/null 2>&1; then + date -u -d "${iso}" +%s 2>/dev/null || echo 0 + else + date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "${iso}" +%s 2>/dev/null || echo 0 + fi +} + +# ----- Lock management ----- +acquire_lock() { + # Returns 0 on success, 1 if held by another live process. + + # Dead-lock cleanup: if the lock file exists and is older than LOCK_TTL_MIN, remove it. + if [ -f "${LOCK_FILE}" ]; then + local lock_epoch lock_age_min + if date --version >/dev/null 2>&1; then + lock_epoch=$(stat -c %Y "${LOCK_FILE}" 2>/dev/null || echo 0) + else + lock_epoch=$(stat -f %m "${LOCK_FILE}" 2>/dev/null || echo 0) + fi + lock_age_min=$(( ($(now_epoch) - lock_epoch) / 60 )) + if [ "${lock_age_min}" -gt "${LOCK_TTL_MIN}" ]; then + log "stale lock detected (age ${lock_age_min}min) — releasing" + rm -f "${LOCK_FILE}" + fi + fi + + # flock-based locking (preferred) + if command -v flock >/dev/null 2>&1; then + exec 9>"${LOCK_FILE}" + if ! flock -n 9; then + return 1 + fi + printf 'pid=%s\nstarted_at=%s\n' "$$" "$(now_iso)" >&9 + return 0 + fi + + # Atomic mkdir fallback (POSIX-portable) + if mkdir "${LOCK_FILE}.d" 2>/dev/null; then + printf 'pid=%s\nstarted_at=%s\n' "$$" "$(now_iso)" > "${LOCK_FILE}" + return 0 + fi + return 1 +} + +release_lock() { + rm -f "${LOCK_FILE}" + rmdir "${LOCK_FILE}.d" 2>/dev/null || true +} + +# ----- Conditions check ----- +conditions_met() { + local state hours_since sessions last_run last_run_epoch + state=$(read_state) + last_run=$(json_get last_run_at "${state}") + sessions=$(json_get sessions_since_last_dream "${state}") + sessions=${sessions:-0} + + # Never run yet → conditions met if at least MIN_SESSIONS observed + if [ -z "${last_run}" ] || [ "${last_run}" = "null" ]; then + [ "${sessions}" -ge "${MIN_SESSIONS}" ] && return 0 + return 1 + fi + + last_run_epoch=$(iso_to_epoch "${last_run}") + hours_since=$(( ($(now_epoch) - last_run_epoch) / 3600 )) + + if [ "${hours_since}" -ge "${MIN_HOURS}" ] && [ "${sessions}" -ge "${MIN_SESSIONS}" ]; then + return 0 + fi + return 1 +} + +# ----- Increment session counter ----- +increment_sessions() { + local state sessions first_session new_state + state=$(read_state) + sessions=$(json_get sessions_since_last_dream "${state}") + sessions=${sessions:-0} + first_session=$(json_get first_session_at "${state}") + [ -z "${first_session}" ] || [ "${first_session}" = "null" ] && first_session="$(now_iso)" + local last_run total + last_run=$(json_get last_run_at "${state}") + total=$(json_get total_dreams "${state}") + total=${total:-0} + new_state=$(cat </dev/null 2>&1; then + log "claude CLI not found — writing pending-trigger marker" + printf '%s\n' "$(now_iso)" > "${STATE_DIR}/auto-dream.pending" + return 0 + fi + + log "conditions met — spawning background dream cycle" + ( + nohup claude --headless --no-interactive 'lovecraft memory dream' \ + >> "${LOG_FILE}" 2>&1 & + disown 2>/dev/null || true + ) & +} + +# ----- Subcommands ----- +cmd_tick() { + # Always increment first. + increment_sessions + + # Then attempt to acquire lock and check conditions. + if ! acquire_lock; then + log "lock held by another instance — skipping" + exit 0 + fi + trap release_lock EXIT + + if conditions_met; then + spawn_dream + fi +} + +cmd_force() { + if ! acquire_lock; then + echo "🚨 auto-dream: lock held — another instance is running. Exit." >&2 + exit 1 + fi + trap release_lock EXIT + log "manual force trigger" + spawn_dream +} + +cmd_status() { + local state last_run sessions total first hours_since + state=$(read_state) + last_run=$(json_get last_run_at "${state}") + sessions=$(json_get sessions_since_last_dream "${state}") + total=$(json_get total_dreams "${state}") + first=$(json_get first_session_at "${state}") + + echo "ulk Auto Dream — status" + echo " state file : ${STATE_FILE}" + echo " first session : ${first:-}" + echo " last dream cycle : ${last_run:-}" + echo " sessions since : ${sessions:-0}" + echo " total cycles : ${total:-0}" + echo " thresholds : ${MIN_HOURS}h / ${MIN_SESSIONS} sessions" + + if [ -n "${last_run}" ] && [ "${last_run}" != "null" ]; then + hours_since=$(( ($(now_epoch) - $(iso_to_epoch "${last_run}")) / 3600 )) + echo " hours since last : ${hours_since}h" + fi + + if conditions_met; then + echo " conditions : ✅ MET (next session triggers a cycle)" + else + echo " conditions : ⏸ not yet" + fi + + if [ -f "${LOCK_FILE}" ]; then + echo " lock : 🔒 held" + fi +} + +cmd_reset() { + # Reset counter (used by lovecraft mode=dream after a successful cycle) + local state total first new_state + state=$(read_state) + total=$(json_get total_dreams "${state}") + total=${total:-0} + first=$(json_get first_session_at "${state}") + [ -z "${first}" ] || [ "${first}" = "null" ] && first="$(now_iso)" + new_state=$(cat <&2 + exit 64 + ;; +esac