CommunityRédaction et éditiongithub.com

Texarkanine/stockroom

Un entrepôt local, fiable et consultable de votre historique de codage agentique.

Qu'est-ce que stockroom ?

La compétence `sr-query` permet d’exécuter des requêtes SQL **en lecture seule** sur une base DuckDB locale, qui agrège l’historique des sessions Cursor et Claude Code. Elle est particulièrement adaptée aux recherches exactes ou structurées : filtres, comptages, regroupements (token sums, date ranges), jointures entre sessions, messages et appels d’outils. La base est construite pour être inoffensive — toute tentative d’écriture est rejetée par DuckDB. Pour les recherches sémantiques (par exemple "trouve les conversations sur les tests flaky"), il ne faut pas utiliser `sr-query` mais la compétence `sr-semantic` ; le choix entre les deux est géré par la compétence `sr-search`. L’outil de requêtage s’appelle `stockroom query` et supporte les formats `tsv` (par défaut), `json` et `table`, ainsi que des niveaux de détail (`compact`, `snippet`, `full`, `raw`) pour éviter d’inonder le contexte avec de larges champs texte. Des garde-fous intégrés empêchent les requêtes trop larges et signalent les erreurs courantes (base manquante, SQL invalide).

Compatible avec✓Claude Code~Codex CLI✓Cursor✓Antigravity
npx skills add Texarkanine/stockroom

Installed? Explore more Rédaction et édition skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

sr-query

sr-query exécute du SQL en lecture seule sur l’entrepôt stockroom — la base de données DuckDB monofichier de votre historique capturé de Cursor et Claude Code. La surface est en lecture seule par construction : DuckDB rejette toute écriture tentée via cette interface, vous ne pouvez donc rien corrompre en interrogeant.

Quand utiliser sr-query

Utilisez sr-query lorsque la question a une forme connue et que la réponse est une recherche exacte ou structurée sur des champs que vous pouvez nommer :

  • Une ligne spécifique par id (WHERE message_id = '…', WHERE session_id = '…').
  • Filtres, comptages et regroupements (COUNT(*), GROUP BY harness, WHERE harness = 'claude', sommes de tokens, plages de dates).
  • Regroupements de tokens par session via la VUE session_token_usage (préférez cette vue à un SUM manuel sur messages).
  • Jointures entre sessions / messages / tool_calls.
  • "Combien", "quelles sessions", "liste les appels d'outils", "quels modèles" — tout ce qui peut s'exprimer en SQL sur des colonnes nommées.

N'utilisez pas sr-query pour une recherche basée sur le sens ("trouve les conversations sur les tests flaky"). SQL ILIKE est une correspondance textuelle littérale, pas une recherche sémantique — pour le sens, utilisez la compétence sr-semantic. Quand vous n'êtes pas sûr de la bonne compétence, le jugement appartient à la compétence sr-search.

Comment invoquer le moteur

stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"

Vous pouvez aussi envoyer du SQL depuis l'entrée standard avec - :

echo "SELECT count(*) FROM messages" | stockroom query -

Si command -v stockroom échoue, la machine n'est pas configurée : dites à l'utilisateur d'exécuter la compétence sr-initialize, et n'essayez aucune autre invocation.

Discipline de sortie : --format et --detail

Les valeurs par défaut sont déjà sûres pour un agent — un appel simple donne une sortie bornée et analysable — donc utilisez les drapeaux seulement quand la situation le nécessite.

--format — forme de la sortie, défaut tsv

ValeurFormeUtilisez-le quand
tsv (défaut)Ligne d'en-tête + lignes séparées par des tabulations, pas de compteur de lignesPar défaut. Adapté au flux pour vous et pour les pipes Unix (cut, awk, wc -l).
jsonUn seul objet {"columns": [...], "rows": [...]}Un utilisateur demande une sortie structurée/machine, ou vous voulez la passer à jq.
tableColonnes alignées en ASCII avec une ligne (N rows)Un utilisateur demande quelque chose de lisible / une commande copier-coller à regarder.

Commencez par le défaut tsv. Proposez --format table ou --format json quand l'utilisateur le demande pour une sortie lisible ou structurée — "ces autres formes existent si vous les voulez."

--detail — largeur par champ, défaut snippet

Les champs chaîne larges (notamment messages.text et tool_calls.tool_input) sont tronqués à la lecture pour qu'une colonne volumineuse n'inonde pas votre contexte. La troncation est purement d'affichage — le texte complet est toujours accessible.

ValeurBudget par champUtilisez-le quand
compact~40 caractèresParcourir de nombreuses lignes candidates à bas coût avant d'en choisir une.
snippet (défaut)~120 caractèresPar défaut. Assez pour reconnaître une ligne sans la décharger entièrement.
fullillimité, sur une seule ligneVous avez besoin de toute la longueur du champ, et le repli sur une ligne convient (tableaux/TSV restent sûrs).
rawillimité, espaces exactsVous avez besoin du texte tel que stocké — retours à la ligne et espaces internes intacts. À privilégier avec --format json.

Un champ dépassant le budget est élagué avec un marqueur indiquant combien de caractères ont été cachés, par exemple …(+2284). Ce marqueur est votre signal que plus de contenu existe : si vous en avez besoin, récupérez uniquement ce champ pour la ligne spécifique à --detail full (longueur) ou --format json --detail raw (espaces exacts) — voir les garde-fous.

Garde-fous

Ce sont les modes de défaillance que cette compétence existe pour empêcher :

  • N'inondez pas votre contexte. Ne faites jamais SELECT * (ou une large colonne text) à --detail full/raw sur de nombreuses lignes. Au lieu de cela : scannez étroitement à --detail compact/snippet avec une liste de colonnes explicite et un LIMIT, identifiez la ligne que vous voulez, puis récupérez uniquement le champ large de cette ligne avec un WHERE sur son id. Un simple SELECT text FROM messages WHERE message_id = '…' est bon marché ; SELECT * FROM messages --detail full est une bombe de contexte. Quand vous avez besoin exactement des espaces (tableaux markdown, blocs de code), utilisez --format json --detail raw.
  • C'est en lecture seule — n'essayez jamais d'écrire. INSERT / UPDATE / DELETE / CREATE échouent tous (query failed: …). Ne réessayez pas une écriture avec une formulation différente ; l'interface ne fait qu'interroger.
  • tool_input est un JSON hétérogène. Ses clés varient selon l'outil (Shell a command, Read a path, …), donc un tool_input->>'key' naïf peut lever une erreur de cast. Extrayez en toute sécurité en filtrant d'abord tool_name dans une sous-requête/CTE et en utilisant la fonction explicite :
stockroom query "SELECT json_extract_string(tool_input, '\$.command') AS cmd
  FROM (SELECT tool_input FROM tool_calls WHERE tool_name = 'Shell') LIMIT 5"

Gérez les erreurs sans vous acharnier

Chaque échec est un message propre sur stderr + un code de sortie — lisez-le et prenez l'action correspondante, ne bouclez pas :

MessageSortieCe que cela signifie / action suivante
error: empty query (…)2Aucun SQL n'a été passé. Fournissez une requête.
error: no warehouse found at … — run \stockroom ingest` first`1L'entrepôt n'a pas été construit. Dites à l'utilisateur d'exécuter stockroom ingest (ou sr-initialize si la machine n'a jamais été configurée) ; ne relancez pas la requête.
query failed: …1SQL invalide, ou une écriture a été rejetée. Corrigez la requête (lisez le message DuckDB) — ne réexécutez pas le même SQL.

Ce qui se trouve dans l'entrepôt

Découvrez d'abord le schéma en direct — cela reste correct à mesure que le schéma évolue :

stockroom query "SELECT table_name, column_name, data_type
  FROM information_schema.columns ORDER BY table_name, ordinal_position"

Référence rapide des colonnes importantes depuis les migrations 0001–0008 (confirmez avec la requête d'introspection ci-dessus) :

  • sessions — une ligne par conversation. harness ('cursor'|'claude'), session_id, project_id (slug du répertoire projet — la clé de regroupement ; pour les chats Cursor CLI, c'est le répertoire de hachage des chats), cwd (chemin réel, peut être NULL), workspace_key (regroupement de chemins multi-harnais, peut être NULL), entrypoint (provenance de surface du harnais quand connue ; peut être NULL — utilisez SELECT DISTINCT entrypoint plutôt que de deviner des littéraux), models (VARCHAR[], Cursor uniquement), title, git_branch, is_subagent, parent_session_id, started_at, ended_at, source_mtime (dernière modification de la transcription source à la dernière ingestion), et quatre champs BIGINT de tokens granulaires pour la session (input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens — remplis uniquement quand un harnais rapporte l'utilisation au niveau de la conversation ; Claude/Cursor les laissent NULL). PK (harness, session_id).
  • messages — une ligne par tour. message_id = '{session_id}#{ordinal}' (uniforme entre les harnais), parent_id, ordinal, role ('user'|'assistant'), text (complet ; la pensée n'est pas capturée), model (par message, Claude uniquement), ts, first_seen_at (première observation par stockroom), et quatre champs BIGINT de tokens : input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens (utilisation des messages Claude ; Cursor NULL). PK (harness, session_id, message_id).
  • session_token_usage — VUE sur les sessions + sommes de tokens des messages. Préférez-la pour les regroupements de conversation. Colonnes : *_from_messages (SUM), *_native (colonnes de session), *_total (COALESCE(native, from_messages)), token_grain ('session'|'message'|'none'). Les totaux sont des regroupements dans l'entrepôt des champs rapportés, pas des factures de fournisseur. N'ajoutez pas non plus de SUM sur les tokens de message par-dessus *_total.
  • tool_calls — entrées d'outils uniquement (jamais les sorties). message_id (le tour qui l'a émise), ordinal, tool_name, tool_input (JSON hétérogène, stocké intégralement — voir le garde-fou). PK (harness, session_id, message_id, ordinal).
  • embeddings — vecteurs par chunk pour la recherche sémantique (owner_table, owner_id, chunk_index, embed_model, vector FLOAT[384]). Vous interrogez rarement cela directement — utilisez la compétence sr-semantic.
  • _sync_state — comptabilité de la marque d'ingestion ; pas intéressant à interroger.

Une règle d'identité à connaître avant d'écrire une jointure : joignez toujours sur le message_id / (harness, session_id) uniforme, jamais sur les colonnes de provenance source_*. Une valeur qui n'existe qu'à une granularité par harnais est honnêtement NULL pour l'autre (par exemple, messages.model est Claude uniquement ; sessions.models est Cursor uniquement ; la même honnêteté de double granularité s'applique aux tokens).

Exemples concrets

Tous vérifiés contre un entrepôt réel.

# Quels harnais sont présents ? (tsv par défaut)
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"

# Nombre de sessions par harnais, lisible pour l'utilisateur :
stockroom query --format table "SELECT harness, count(*) AS n FROM sessions GROUP BY harness ORDER BY n DESC"

# Outils les plus actifs :
stockroom query "SELECT tool_name, count(*) AS calls FROM tool_calls GROUP BY tool_name ORDER BY calls DESC LIMIT 5"

# Parcourir les messages candidats à bas coût, puis récupérer le message choisi en entier :
stockroom query --detail compact "SELECT message_id, text FROM messages WHERE text ILIKE '%flaky test%' LIMIT 10"
stockroom query --format json --detail raw \
  "SELECT text FROM messages WHERE message_id = '<id-from-the-scan>'"

# Sortie structurée pour un utilisateur/outil :
stockroom query --format json "SELECT harness, session_id, title FROM sessions WHERE title IS NOT NULL LIMIT 5"

# Regroupements de tokens par session (VUE session_token_usage) :
stockroom query --format table \
  "SELECT harness, session_id, input_tokens_total, output_tokens_total, token_grain
   FROM session_token_usage
   ORDER BY input_tokens_total DESC NULLS LAST
   LIMIT 10"

Plus de variantes (outils sans limite avec fenêtre d'activité, regroupements de tokens par jour/harnais, SQL d'utilisation de compétence par harnais) : voir le Cahier de recettes.

Cahier de recettes

SQL de démarrage coriace : references/cookbook/. Ouvrez l'index, puis seulement la recette dont vous avez besoin.

Transmission à un humain

Vous êtes l'opérateur de l'outil, pas son affichage. Exécutez le SQL, lisez le résultat, et répondez à l'utilisateur en langage naturel. Ne collez pas du tsv brut à un humain sauf s'il a demandé à le voir. Quand il veut la sortie brute — ou une commande à exécuter lui-même — donnez-lui une variante --format table (pour lire) ou --format json (pour traiter).

Comprendre le système

Pour comprendre pourquoi ces contrats sont comme ils sont — le packaging, le contrat de torche, la construction en lecture seule, la doctrine de troncature, l'identité/la provenance — lisez le modèle système partagé : ../sr-search/references/system-model.md.

Skills associés