Spec : système memory_sessions
Fichier auto-porté — contient tout le nécessaire pour installer ce système dans n'importe quel projet Claude Code.
Modèle d'installation
Modèle recommandé pour installer cette spec : Sonnet
L'installation nécessite d'adapter la liste des projets, de créer les répertoires et de modifier settings.json — un peu de jugement contextuel est utile.
Présentation
Nom : memory_sessions
Type : Stop hook + 2 skills
Rôle : Mémoire automatique de session — après chaque tour Claude, Haiku analyse l'échange et met à jour des logs quotidiens et une mémoire long terme par projet.
Modèle utilisé : claude-haiku-4-5-20251001 (rapide, économique, suffisant pour extraction/résumé)
Pourquoi ce système ?
Un projet Claude Code actif accumule du contexte : décisions techniques, comportements à éviter, état d'avancement de tâches longues. Sans mémoire persistante, chaque nouvelle session repart de zéro.
Les solutions existantes (CLAUDE.md, mémoire auto de Claude Code) ont des limites :
- CLAUDE.md : règles statiques, pas de trace d'activité quotidienne
- Mémoire auto Claude Code (~/.claude/...) : globale, non versionnée, non portable entre machines
Ce système apporte une mémoire structurée par projet, versionnée dans le dépôt, consultable via /memory_load, consolidable via /memory_promotion.
Inspiration : OpenClaw
Inspiré de l'architecture mémoire d'OpenClaw (agent Claude open-source) :
- Trois niveaux : mémoire immédiate → logs quotidiens → mémoire long terme
- Déclenchement automatique en fin de session via hook
- Fichiers plats Markdown, lisibles et éditables directement
Différences délibérées par rapport à OpenClaw :
- Les logs quotidiens sont modifiables (OpenClaw les rend append-only) — permet à Haiku de réorganiser l'entrée du jour
- Structure par projet (pas une mémoire globale) — adapté aux workspaces multi-projets
- Le hook appelle claude depuis /tmp pour éviter que le CLAUDE.md du projet hôte interfère avec la réponse Haiku
Architecture
Structure de fichiers
{project_root}/
memory_sessions/ ← mémoire du projet père
.gitkeep
long_memory.md ← mémoire long terme (condensé permanent)
logs/
2026-03-21.md ← log quotidien structuré par sections
2026-03-20.md
.debug/ ← JSON brut Haiku (gitignored)
2026-03-21_190023.json
archives/ ← logs > 30 jours (créé par /memory_promotion)
projects/{sous-projet}/
memory_sessions/ ← même structure, par sous-projet
.gitkeep
long_memory.md
logs/
.debug/
# Log session — 2026-03-21
## Contexte
- Sujet ou système sur lequel porte la session (1-2 phrases, stable dans la journée)
## Objectif
- Intention de l'échange
## Problèmes rencontrés
- Bugs, blocages avec cause/solution
## Connaissances acquises
- Faits techniques/métier durables
## Travail produit
- Ce qui a été livré, créé, modifié
## Décisions prises
- Choix techniques ou fonctionnels actés
## À suivre
- Points ouverts, prochaines étapes
## Finalisé
- ✅ Points résolus (déplacés depuis "À suivre" via OK-done)
{
"updates": [
{
"project": "cmms_doc",
"sections": {
"contexte": ["Refonte du hook memory-session.sh pour passer au format JSON structuré."],
"objectif": ["Vérifier la découverte des skills."],
"problemes": ["Recherche des skills échouée : aucun skill trouvé."],
"connaissances": [],
"travail": ["Rédaction spec mémoire de session."],
"decisions": [],
"a_suivre": ["Revalider découverte skills."],
"a_suivre_done": []
},
"long_memory_update": null
}
]
}
| Clé JSON |
Titre Markdown |
Contenu |
Comportement fusion |
contexte |
## Contexte |
Sujet/système de la session (1-2 phrases) |
Append (dédupliqué) — plusieurs contextes possibles par jour |
objectif |
## Objectif |
Ce sur quoi porte la session |
Append (dédupliqué) |
problemes |
## Problèmes rencontrés |
Bugs, blocages, erreurs — avec cause/solution si connue |
Append (dédupliqué) |
connaissances |
## Connaissances acquises |
Faits techniques ou métier appris/confirmés |
Append (dédupliqué) |
travail |
## Travail produit |
Ce qui a été livré, créé, modifié |
Append (dédupliqué) |
decisions |
## Décisions prises |
Choix techniques ou fonctionnels actés |
Append (dédupliqué) |
a_suivre |
## À suivre |
Points ouverts, prochaines étapes |
Append (dédupliqué) |
a_suivre_done |
(pas de section propre) |
Points de "À suivre" à marquer comme résolus → déplacés vers "Finalisé" |
Retrait de "À suivre" + ✅ dans "Finalisé" |
Gestion "OK-done"
Quand l'utilisateur envoie exactement OK-done (insensible à la casse) :
- Haiku reçoit une instruction spécifique pour marquer les points de "À suivre" comme finalisés
- Les items correspondants sont retirés de "## À suivre" et déplacés dans "## Finalisé" avec un préfixe ✅
- Toutes les autres sections sont vides ([])
Debug JSON
Le JSON brut retourné par Haiku est copié dans memory_sessions/logs/.debug/YYYY-MM-DD_HHMMSS.json (un fichier par appel, horodaté). Ce répertoire est dans .gitignore (**/.debug/).
Règles de taille
| Fichier |
Règle |
logs/YYYY-MM-DD.md |
Structuré par sections — append de nouveaux bullets sans duplication |
long_memory.md |
Maximum 300 lignes — résumé automatique si dépassement (via /memory_promotion) |
| Archive |
Logs > 30 jours déplacés vers logs/archives/ |
.debug/ |
JSON brut Haiku, gitignored, utile pour diagnostic |
Composants
| Composant |
Fichier |
Déclenchement |
| Stop hook |
.claude/hooks/memory-session.sh |
Automatique — fin de chaque tour Claude |
| Skill load |
.claude/skills/memory_load/SKILL.md |
Manuel — /memory_load [projet] |
| Skill promotion |
.claude/skills/memory_promotion/SKILL.md |
Manuel — /memory_promotion [projet] |
Flux du hook
Claude répond
↓
Stop hook déclenché (settings.json)
↓
memory-session.sh reçoit JSON stdin
↓
stop_hook_active = true ? → exit 0 (anti-boucle)
↓
Extraire transcript_path du JSON stdin
↓
Étape 1 — Parser le JSONL : trouver le dernier échange
(ignorer les messages tool_result/thinking/tool_use — texte vide)
↓
Étape 2 — Lire les logs existants du jour pour chaque projet
+ Détecter si l'utilisateur a envoyé "OK-done"
+ Construire prompt structuré pour Haiku
↓
Étape 3 — Appeler Haiku depuis /tmp
(depuis /tmp : évite que CLAUDE.md du projet interfère)
↓
Haiku retourne JSON structuré par sections :
{ project, sections: { objectif, problemes, connaissances,
travail, decisions, a_suivre, a_suivre_done }, long_memory_update }
↓
Étape 4 — Python fusionne les nouvelles entrées dans le log existant
+ Parse les sections Markdown existantes
+ Append les nouveaux bullets sous chaque section
+ Gère a_suivre_done → déplace vers "Finalisé" avec ✅
+ Copie le JSON brut dans .debug/ pour diagnostic
+ Écrit long_memory.md si nécessaire
(avec flock si disponible, dégradation gracieuse sinon)
↓
exit 0
Le transcript est un fichier JSONL. Les messages utiles ont type: "user" ou type: "assistant" avec message.content :
{
"type": "user",
"message": {
"role": "user",
"content": [
{ "type": "text", "text": "le texte du message" }
]
}
}
Important : beaucoup de messages intermédiaires ont type: "tool_result", "thinking", "tool_use" avec text vide — il faut filtrer sur content[].type == "text" et ignorer les messages sans texte.
Anti-boucle
Quand le Stop hook appelle claude, cette invocation se termine → déclenche à nouveau le Stop hook. Claude Code injecte stop_hook_active: true dans le JSON stdin de la seconde invocation. Le hook vérifie ce flag en premier et fait exit 0 immédiatement.
Choix d'implémentation
| Choix |
Décision |
Raison |
| Modèle |
Haiku 4.5 |
Rapide (~1-2s), coût minimal, suffisant pour extraction/classification |
Appel Claude depuis /tmp |
Oui |
Le CLAUDE.md du projet hôte interdit à Haiku de répondre en JSON pur |
| Format intermédiaire |
JSON structuré par sections → Python formate le Markdown |
Séparation claire extraction/formatage, debug facile |
| Déduplication |
Haiku relit le log existant + append sous la même section |
Pas de réécriture/fusion — robuste et simple |
flock |
Optionnel (dégradation gracieuse) |
Non installé par défaut sur macOS — brew install util-linux |
| Fichiers temporaires |
mktemp -d avec trap EXIT |
Évite l'injection de variables shell dans les heredocs Python |
| Debug JSON |
Copie dans .debug/ (gitignored) |
Diagnostic facile sans polluer le dépôt |
| Mémoire par projet |
Oui |
Un workspace peut contenir plusieurs sous-projets indépendants |
long_memory.md (pas memory.md) |
Oui |
Évite la collision avec les memory.md existants dans les sous-projets |
memory_sessions/ (pas memory/) |
Oui |
Évite la collision avec les répertoires memory/ existants |
| Versionnement |
Oui (dans git) |
Portabilité entre machines, historique des mémoires |
Prérequis
| Prérequis |
Détail |
Installation |
| Claude Code CLI |
claude disponible dans PATH |
claude.ai/code |
| Python 3 |
Stdlib uniquement (json, pathlib, re) — aucun package tiers |
pré-installé macOS / brew install python |
flock |
Optionnel (dégradation gracieuse si absent) |
brew install util-linux (macOS) / pré-installé Linux |
| Homebrew |
Gestionnaire de paquets macOS — nécessaire pour util-linux |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| Dépôt git |
Pour la portabilité des fichiers memory_sessions/ |
git init ou clone |
Installation complète sur macOS
# 1. Homebrew (si absent)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 2. util-linux (pour flock)
brew install util-linux
# 3. Vérifier que claude est dans PATH
claude --version
Installation dans un nouveau projet
Étape 1 — Copier le hook
Créer .claude/hooks/memory-session.sh avec le contenu ci-dessous (voir section Code complet).
Rendre exécutable :
chmod +x .claude/hooks/memory-session.sh
Étape 2 — Adapter la liste des projets
Dans le hook, modifier la variable PROJECT_PATHS :
PROJECT_PATHS = {
"root": "memory_sessions", # projet père — toujours présent
"mon_projet": "projects/mon_projet/memory_sessions",
# ajouter autant de sous-projets que nécessaire
}
Et dans le prompt Haiku, mettre à jour la liste :
## Projets disponibles
root, mon_projet, autre_projet
Étape 3 — Enregistrer le hook dans settings.json
Ajouter dans .claude/settings.json :
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/memory-session.sh" }
]
}
]
}
}
mkdir -p memory_sessions/logs
touch memory_sessions/.gitkeep
# Répéter pour chaque sous-projet
mkdir -p projects/mon_projet/memory_sessions/logs
touch projects/mon_projet/memory_sessions/.gitkeep
Ajouter dans .gitignore :
# Debug JSON des appels Haiku (memory-session hook)
**/.debug/
Étape 5 — Copier les skills (optionnel mais recommandé)
Créer .claude/skills/memory_load/SKILL.md et .claude/skills/memory_promotion/SKILL.md (voir section Code complet).
Skills
/memory_load [projet]
Charge la mémoire en début de session. Lit long_memory.md + les logs du jour et de la veille. Affiche un résumé consolidé.
/memory_load cmms_doc
/memory_load all
Consolide les logs récents vers long_memory.md. Archive les logs > 30 jours. À lancer manuellement (hebdomadaire ou mensuel).
Code complet
.claude/hooks/memory-session.sh
#!/usr/bin/env bash
# memory-session.sh — Stop hook : met à jour la mémoire de session après chaque tour Claude
# Inspiré d'OpenClaw : mémoire courte (log quotidien) + longue (long_memory.md) par projet
set -euo pipefail
# Anti-boucle : si Claude a déjà traité ce hook, sortir immédiatement
STDIN_DATA=$(cat)
STOP_HOOK_ACTIVE=$(echo "$STDIN_DATA" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
print('true' if d.get('stop_hook_active') else 'false')
except:
print('false')
" 2>/dev/null || echo "false")
[ "$STOP_HOOK_ACTIVE" = "true" ] && exit 0
# Extraire transcript_path depuis stdin
TRANSCRIPT_PATH=$(echo "$STDIN_DATA" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
print(d.get('transcript_path', ''))
except:
print('')
" 2>/dev/null || echo "")
[ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ] && exit 0
PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
TODAY=$(date +%Y-%m-%d)
NOW=$(date +%Y-%m-%d_%H%M%S)
# Répertoire temporaire nettoyé automatiquement à la sortie
TMP_HOOK=$(mktemp -d)
trap 'rm -rf "$TMP_HOOK"' EXIT
# --- Étape 1 : extraire le dernier échange humain+assistant ---
EXCHANGE_FILE="$TMP_HOOK/exchange.json"
python3 - "$TRANSCRIPT_PATH" "$EXCHANGE_FILE" <<'PYEOF'
import sys, json
from pathlib import Path
transcript_path = sys.argv[1]
output_file = sys.argv[2]
messages = []
with open(transcript_path) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
d = json.loads(line)
if d.get("type") in ("user", "assistant"):
msg = d.get("message", {})
content = msg.get("content", "")
if isinstance(content, list):
content = " ".join(
c.get("text", "") for c in content
if isinstance(c, dict) and c.get("type") == "text"
)
messages.append({"role": d["type"], "content": content})
except:
pass
def extract_text(content):
"""Extrait uniquement le texte réel (ignore tool_result, thinking, tool_use)."""
if isinstance(content, str):
return content.strip()
if isinstance(content, list):
return " ".join(
c.get("text", "") for c in content
if isinstance(c, dict) and c.get("type") == "text" and c.get("text", "").strip()
).strip()
return ""
human_msg = ""
assistant_msg = ""
for m in reversed(messages):
role = m.get("role", "")
text = extract_text(m.get("content", ""))
if not text:
continue
if not assistant_msg and role == "assistant":
assistant_msg = text[:2000]
elif assistant_msg and not human_msg and role == "user":
human_msg = text[:1000]
break
Path(output_file).write_text(json.dumps({"human": human_msg, "assistant": assistant_msg}))
PYEOF
# Vérifier que l'échange est non vide
HUMAN_LEN=$(python3 -c "
import json, sys
with open('$EXCHANGE_FILE') as f:
d = json.load(f)
print(len(d.get('human', '')))
" 2>/dev/null || echo "0")
[ "$HUMAN_LEN" = "0" ] && exit 0
# --- Étape 2 : construire le prompt structuré pour Haiku (avec log existant) ---
PROMPT_FILE="$TMP_HOOK/prompt.txt"
python3 - "$EXCHANGE_FILE" "$PROMPT_FILE" "$PROJECT_ROOT" "$TODAY" <<'PYEOF'
import sys, json
from pathlib import Path
exchange_file = sys.argv[1]
prompt_file = sys.argv[2]
project_root = sys.argv[3]
today = sys.argv[4]
with open(exchange_file) as f:
d = json.load(f)
human = d["human"][:800]
assistant = d["assistant"][:1200]
PROJECT_PATHS = {
"root": "memory_sessions",
"mon_projet": "projects/mon_projet/memory_sessions",
# ← ADAPTER ICI
}
# Lire les logs existants du jour pour chaque projet
existing_logs = {}
for proj, path in PROJECT_PATHS.items():
log_file = Path(project_root) / path / "logs" / f"{today}.md"
if log_file.exists():
content = log_file.read_text().strip()
if content:
existing_logs[proj] = content
if existing_logs:
existing_section = "\n\n".join(
f"### {proj}\n{content}" for proj, content in existing_logs.items()
)
else:
existing_section = "(aucun log existant)"
# Détection OK-done
is_ok_done = human.strip().lower().replace("-", " ").replace("_", " ").strip() in ("ok done", "okdone")
if is_ok_done:
special = """
ATTENTION : L'utilisateur a envoyé "OK-done" = le point en cours est finalisé.
Retourne UNIQUEMENT des items dans "a_suivre_done" pour marquer les points résolus.
Toutes les autres sections doivent être des tableaux vides [].
"""
else:
special = ""
prompt = """Tu es un assistant de mémoire de session Claude. Analyse cet échange et classe les informations.
## Échange
Humain: {human}
Claude: {assistant}
{special}
## Projets disponibles
root, mon_projet ← ADAPTER ICI
## Log existant du jour
{existing}
## Sections à remplir
Pour chaque projet concerné, retourne un JSON avec les sections pertinentes.
Chaque section est un tableau de strings (1 string = 1 bullet point).
Un tableau vide [] = rien à ajouter dans cette section.
Règles :
- Ne répète JAMAIS un point déjà présent dans le log existant.
- Sois concret et actionnable : "Test échoué : aucun skill trouvé, cause = extraction texte vide" plutôt que "Test échoué".
- "contexte" : 1-2 phrases décrivant LE SUJET ou LE SYSTÈME sur lequel porte la session (ex: "Refonte du hook memory-session.sh pour passer au format JSON structuré."). Plusieurs contextes possibles par jour — ne pas répéter un contexte déjà présent dans le log existant.
- "objectif" : l'intention de l'échange (un jour peut avoir plusieurs objectifs).
- "a_suivre" : seulement si un point reste explicitement ouvert.
- "a_suivre_done" : si l'échange résout un point listé dans "À suivre" du log existant. Reprends le texte EXACT.
- "connaissances" : uniquement les faits techniques/métier durables.
- "decisions" : choix techniques ou fonctionnels actés.
## Format JSON strict (aucun texte autour)
{{"updates":[{{"project":"...","sections":{{"contexte":[],"objectif":[],"problemes":[],"connaissances":[],"travail":[],"decisions":[],"a_suivre":[],"a_suivre_done":[]}},"long_memory_update":null}}]}}
""".format(human=human, assistant=assistant, existing=existing_section, special=special)
Path(prompt_file).write_text(prompt)
PYEOF
# --- Étape 3 : appel Haiku ---
OUTPUT_FILE="$TMP_HOOK/haiku_output.txt"
# Appel depuis /tmp pour éviter que le CLAUDE.md du projet interfère
(cd /tmp && claude --model claude-haiku-4-5-20251001 --print) \
< "$PROMPT_FILE" > "$OUTPUT_FILE" 2>/dev/null \
|| echo '{"updates":[]}' > "$OUTPUT_FILE"
# --- Étape 4 : écrire les fichiers mémoire (structurés par sections) ---
write_memory() {
python3 - "$PROJECT_ROOT" "$TODAY" "$OUTPUT_FILE" "$NOW" <<'PYEOF'
import sys, json, re, collections, shutil
from pathlib import Path
project_root = sys.argv[1]
today = sys.argv[2]
output_file = sys.argv[3]
now_ts = sys.argv[4]
PROJECT_PATHS = {
"root": "memory_sessions",
"mon_projet": "projects/mon_projet/memory_sessions",
# ← ADAPTER ICI
}
SECTION_ORDER = [
("contexte", "## Contexte"),
("objectif", "## Objectif"),
("problemes", "## Problèmes rencontrés"),
("connaissances", "## Connaissances acquises"),
("travail", "## Travail produit"),
("decisions", "## Décisions prises"),
("a_suivre", "## À suivre"),
("finalise", "## Finalisé"),
]
def parse_sections(md_text):
"""Parse un Markdown en sections ordonnées : titre -> [lignes bullet]."""
sections = collections.OrderedDict()
current_title = None
for line in md_text.split("\n"):
if line.startswith("## "):
current_title = line.strip()
if current_title not in sections:
sections[current_title] = []
elif current_title is not None:
stripped = line.strip()
if stripped.startswith("- "):
sections[current_title].append(stripped)
return sections
def merge_log(existing_md, update, today_str):
"""Fusionne les nouvelles entrées dans le log existant."""
sections = parse_sections(existing_md) if existing_md else collections.OrderedDict()
new_sections = update.get("sections", {})
# Ajouter les nouvelles entrées par section
for key, title in SECTION_ORDER:
if key == "finalise":
continue
items = new_sections.get(key, [])
if items:
if title not in sections:
sections[title] = []
for item in items:
bullet = f"- {item}"
if bullet not in sections[title]:
sections[title].append(bullet)
# Gérer a_suivre_done : retirer de "À suivre", ajouter à "Finalisé"
done_items = new_sections.get("a_suivre_done", [])
if done_items and "## À suivre" in sections:
remaining = []
finalized = []
for line in sections["## À suivre"]:
matched = False
for done in done_items:
if done.lower() in line.lower():
finalized.append(line.replace("- ", "- ✅ ", 1))
matched = True
break
if not matched:
remaining.append(line)
sections["## À suivre"] = remaining
if finalized:
sections.setdefault("## Finalisé", []).extend(finalized)
# Réécrire le Markdown
lines = [f"# Log session — {today_str}", ""]
known_titles = {t for _, t in SECTION_ORDER}
for _, title in SECTION_ORDER:
if title in sections and sections[title]:
lines.append(title)
for item in sections[title]:
lines.append(item)
lines.append("")
# Sections inconnues (au cas où)
for title, items in sections.items():
if title not in known_titles and items:
lines.append(title)
for item in items:
lines.append(item)
lines.append("")
return "\n".join(lines).rstrip() + "\n"
# --- Main ---
raw = Path(output_file).read_text()
try:
match = re.search(r'\{.*\}', raw, re.DOTALL)
data = json.loads(match.group()) if match else {"updates": []}
except Exception:
data = {"updates": []}
for update in data.get("updates", []):
project = update.get("project", "")
if project not in PROJECT_PATHS:
continue
mem_dir = Path(project_root) / PROJECT_PATHS[project]
logs_dir = mem_dir / "logs"
logs_dir.mkdir(parents=True, exist_ok=True)
# Lire le log existant du jour
log_file = logs_dir / f"{today}.md"
existing_md = log_file.read_text() if log_file.exists() else ""
# Fusionner et réécrire si des sections sont non vides
has_sections = update.get("sections") and any(
update["sections"].get(k, []) for k in
["contexte", "objectif", "problemes", "connaissances", "travail", "decisions", "a_suivre", "a_suivre_done"]
)
if has_sections:
merged = merge_log(existing_md, update, today)
log_file.write_text(merged)
# Mémoire long terme (inchangé)
long_memory = (update.get("long_memory_update") or "").strip()
if long_memory:
long_file = mem_dir / "long_memory.md"
if not long_file.exists():
long_file.write_text(f"# Mémoire long terme — {project}\n\n")
with open(long_file, "a") as f:
f.write(f"\n## {today}\n\n{long_memory}\n")
# Debug : copier le JSON brut de Haiku
debug_dir = logs_dir / ".debug"
debug_dir.mkdir(exist_ok=True)
shutil.copy2(output_file, debug_dir / f"{now_ts}.json")
PYEOF
}
# Verrou flock si disponible, sinon continuer sans (dégradation gracieuse)
LOCK_FILE="/tmp/.memory-session-product-agents.lock"
if command -v flock >/dev/null 2>&1; then
(flock -x 200; write_memory) 200>"$LOCK_FILE"
else
write_memory
fi
exit 0
.claude/skills/memory_load/SKILL.md
# /memory_load
Charge la mémoire de session des projets actifs.
## Étape 1 — Identifier les projets
Si argument passé → ce projet uniquement. Sinon → demander via AskUserQuestion.
`all` → tous les projets.
## Étape 2 — Lire les fichiers
Pour chaque projet (chemin : `memory_sessions/` ou `projects/{nom}/memory_sessions/`) :
1. Lire `long_memory.md` (si existant)
2. Lire `logs/{YYYY-MM-DD}.md` du jour courant
3. Lire `logs/{YYYY-MM-DD}.md` du jour précédent
## Étape 3 — Afficher le résumé
✓ Mémoire chargée pour : {projets}
[{Projet}]
Long terme : {contenu ou "vide"}
Aujourd'hui : {entrées ou "aucune"}
Hier : {entrées ou "aucune"}
# /memory_promotion
Consolide les logs vers long_memory.md et archive les anciens.
## Étape 1 — Périmètre
Si argument → ce projet. Sinon → demander. `all` → tous.
## Étape 2 — Analyser les logs (30 derniers jours)
Lire `memory_sessions/logs/*.md` (hors archives/).
## Étape 3 — Mettre à jour long_memory.md
Intégrer les faits durables. Maximum 300 lignes — résumer si dépassement.
## Étape 4 — Archiver
Déplacer logs > 30 jours vers `memory_sessions/logs/archives/`.
## Étape 5 — Résumé
Afficher : logs analysés, faits promus, fichiers archivés, taille long_memory.md.
Bugs connus et solutions
| Bug |
Cause |
Solution |
--stop-hook-active invalide |
Ce flag n'existe pas dans le CLI claude — il est uniquement dans le JSON stdin du hook |
Supprimé du call CLI |
| CLAUDE.md interfère |
Quand claude est appelé depuis le projet, le CLAUDE.md du projet est injecté et modifie la réponse Haiku |
Appeler claude depuis /tmp |
| Messages vides extraits |
Le transcript JSONL contient beaucoup de messages tool_result/thinking avec text vide |
Filtrer sur content[].type == "text" et ignorer les messages sans texte réel |
flock absent (macOS) |
flock vient de util-linux, non installé par défaut sur macOS |
Dégradation gracieuse : if command -v flock avant d'utiliser |
| Variable shell dans heredoc Python |
"""$VAR""" dans un heredoc casse si $VAR contient des guillemets ou caractères spéciaux |
Utiliser des fichiers temporaires (mktemp) passés en arguments |
--no-streaming invalide |
Ce flag n'existe pas dans le CLI claude |
Supprimé — --print suffit |