---
name: historique-projet
description: Générer ou mettre à jour un historique des améliorations d'un projet à partir des sessions OpenCode (opencode.db), au format log chronologique avec tags de type, et l'insérer dans la section "Suivie et améliorations" des pages projets SilverBullet.
---

# Skill: historique-projet

# Génération d'historique projet à partir des sessions OpenCode

## Objectif

Produire un log des améliorations réalisées sur un projet à partir des sessions OpenCode, jour par jour, avec tags de type pour filtrage.

## Format

* [[01_JOURNAL/2026/2026-MM-DD]] - #TAG : évolution réalisée -> [[liens vers sources, documentation]]

## Tags (nomenclature)

| Tag | Usage |
|---|---|
| #VPS-Securisation | SSH, fail2ban, audit, secrets, ntfy |
| #VPS-MCP | serveurs MCP (Silverbullet, Excalidraw…) |
| #VPS-Skill | création / mise à jour de skills |
| #VPS-Agent | création / mise à jour d'agents |
| #VPS-OpenCode | config OpenCode, contexte global, mémoire |
| #VPS-Script | scripts (costs, sync, consolidation…) |
| #VPS-Infra | système : mises à jour, upload, logrotate |
| #VPS-PKM | organisation PKM, réunions, journal |

Règle : 1 tag principal par ligne (parfois 2 si pertinent) pour des requêtes simples.

## Méthode

1. Lister les sessions depuis la base SQLite OpenCode (tables session_v2 / session_message) :

```sql
sqlite3 ~/.local/share/opencode/opencode.db \
  "SELECT id, title, agent, date(time_created/1000,'unixepoch','localtime') AS jour \
   FROM session_v2 WHERE time_created >= strftime('%s','2026-10-07 00:00:00') \
   ORDER BY time_created;"
```

2. Extraire le fil de chaque session (messages user + assistant) depuis session_message (colonne data JSON, champ text / content[].text), en datant chaque échange via time.created des messages user.

3. Identifier les améliorations, les regrouper par jour, rédiger au format log avec 1 tag principal par ligne.

4. Insérer dans la section "## Suivie et améliorations" des pages projets.

5. Ajouter la table unique des évolutions (les tags sur lignes de liste sont indexés et requêtables via index.items()) :

```lua
${query[[
  from t = index.items()
  where t.page == _CTX.currentPage.name
    and string.startsWith(t.text, "[[01_JOURNAL/2026/")
  order by t.text desc
  select {
    REF = "[[" .. t.ref .. "]]",
    DATE = "[[01_JOURNAL/2026/" .. string.match(t.text, "(%d%d%d%d%-%d%d%-%d%d)") .. "]]",
    TAGS = table.concat(t.tags, ", "),
    TEXT = t.text
  }
]]}
```

> Syntaxe SLIQ / `eval_lua` : cf. skill `silverbullet-mcp` — section « ⭐ Requêtes SLIQ / Lua (eval_lua) ».

## Rappel dans les sessions passées (niveau M)

Pour « on en a parlé quand ? » — recherche plein texte dans les messages de **toutes** les sessions OpenCode. Coût nul tant qu'on ne cherche pas (0 token à l'usage, base locale).

```sql
sqlite3 ~/.local/share/opencode/opencode.db \
  "SELECT s.title, s.agent, date(s.time_created/1000,'unixepoch','localtime') AS jour,
          substr(sm.data, 1, 160)
   FROM session_message sm
   JOIN session_v2 s ON s.id = sm.session_id
   WHERE sm.data LIKE '%<mot-clé>%' COLLATE NOCASE
   ORDER BY sm.time_created DESC LIMIT 10;"
```

- `session_message.data` = JSON `{"time":{"created":...},"text":"..."}` — chercher le texte tel quel (LIKE, insensible à la casse via `COLLATE NOCASE`).
- Filtrer par agent : `AND s.agent = 'vps-admin'` (ou `'pkm-manager'`).
- Toujours `LIMIT 10` et ne lire que le début (`substr`) pour garder l'affichage léger.
- Les dates sont en epoch ms (`time_created`), converties en localtime.

## Consultation de la mémoire long terme (niveau L)

**Règle stricte : jamais lire la page `80_CONFIG/IA_MEMOIRE_LONGTERME` en entier** — elle grossit avec les consolidations. Toujours interroger les **items indexés** via SLIQ / `eval_lua` (les entrées LT sont des lignes de liste `- [[...]] - #tag : texte` indexées automatiquement par SilverBullet). Syntaxe : cf. skill `silverbullet-mcp`, section « ⭐ Requêtes SLIQ / Lua (eval_lua) ».

```lua
-- Ex. : toutes les entrées LT du domaine « OpenCode & mémoire » (tag VPS-OpenCode)
query[[
  from t = index.items()
  where t.page == "80_CONFIG/IA_MEMOIRE_LONGTERME"
    and table.includes(t.tags, "VPS-OpenCode")
  select { DATE = string.match(t.text, "(%d%d%d%d%-%d%d%-%d%d)"), TEXT = t.text }
]]
```

- Filtres possibles : par tag (`table.includes(t.tags, "...")`), par mot-clé (`string.contains(t.text, "...")`), par date (regex sur le lien `[[01_JOURNAL/YYYY/YYYY-MM-DD]]`).
- Toujours borner : `limit` si la recherche est large ; ne sélectionner que les champs utiles (coût = seules les lignes filtrées, indépendant de la taille de la page).
- Si le domaine n'est **pas** listé dans l'`[INDEX_LT]` de `memory-common.md` → **ne rien consulter** (0 token).

## Points d'attention

- Mise à jour incrémentale : ajouter une ligne datée au fil de l'eau, pas besoin de reclasser.
- Le lien date pointe vers le journal (contexte du jour) ; les jours sans page créent un lien rouge cliquable.
- La table unique permet de filtrer/trier par tag, date ou texte ; le log reste la source de vérité chronologique.
- Ne jamais afficher un secret (token, mot de passe) dans le log ou les liens.
