sr-query
sr-query ejecuta SQL de solo lectura contra el almacén stockroom — la base de datos DuckDB de un solo archivo que contiene tu historial capturado de Cursor + Claude Code. La superficie es de solo lectura por construcción: DuckDB rechaza cualquier escritura que se intente a través de ella, por lo que no puedes corromper nada al consultar.
Cuándo usar sr-query
Recurre a sr-query cuando la pregunta tenga una forma conocida y la respuesta sea una búsqueda exacta o estructurada sobre campos que puedas nombrar:
- Una fila específica por id (
WHERE message_id = '…',WHERE session_id = '…'). - Filtros, conteos y resúmenes (
COUNT(*),GROUP BY harness,WHERE harness = 'claude', sumas de tokens, rangos de fechas). - Resúmenes de tokens por sesión mediante la VISTA
session_token_usage(prefiere esto sobre unSUMhecho a mano enmessages). - Uniones entre
sessions/messages/tool_calls. - "Cuántos", "qué sesiones", "lista las llamadas a herramientas", "qué modelos" — cualquier cosa que puedas expresar como SQL sobre columnas con nombre.
No uses sr-query para recuperación basada en significado ("encuentra conversaciones sobre pruebas inestables"). SQL ILIKE es una coincidencia literal de subcadena, no búsqueda semántica — para significado, usa el skill sr-semantic. Cuando no estés seguro de cuál es el correcto, ese juicio le corresponde al skill sr-search.
Cómo invocar el motor
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"
También puedes canalizar SQL desde stdin con -:
echo "SELECT count(*) FROM messages" | stockroom query -
Si command -v stockroom falla, la máquina no está configurada: dile al usuario que ejecute el skill sr-initialize, y no intentes ninguna otra invocación.
Disciplina de salida: --format y --detail
Los valores predeterminados ya son seguros para un agente — una llamada simple produce una salida acotada y analizable — así que recurre a las banderas solo cuando la situación lo requiera.
--format — forma de la salida, por defecto tsv
| Valor | Forma | Úsalo cuando |
|---|---|---|
tsv (predeterminado) | Fila de encabezado + filas separadas por tabuladores, sin contador de filas | Predeterminado. Amigable para flujo y para pipelines unix (cut, awk, wc -l). |
json | Un único objeto {"columns": [...], "rows": [...]} | Un usuario pide salida estructurada/máquina, o quieres pasársela a jq. |
table | ASCII alineado por columnas con un pie de (N filas) | Un usuario pide algo legible para humanos / un comando copiable para mirar. |
Empieza con el predeterminado tsv. Ofrece --format table o --format json cuando el usuario lo pida para salida legible o estructurada — "estas otras formas existen si las quieres".
--detail — ancho por campo, por defecto snippet
Los campos de cadena anchos (notablemente messages.text y tool_calls.tool_input) se truncan en tiempo de lectura para que una columna grande no inunde tu contexto. El truncamiento es solo de visualización — el texto completo siempre se puede recuperar.
| Valor | Presupuesto por campo | Úsalo cuando |
|---|---|---|
compact | ~40 caracteres | Escaneando muchas filas candidatas de forma barata antes de elegir una. |
snippet (predeterminado) | ~120 caracteres | Predeterminado. Suficiente para reconocer una fila sin volcarla. |
full | sin límite, una sola línea | Necesitas toda la longitud del campo, y el colapso en una línea está bien (las tablas/TSV se mantienen seguros). |
raw | sin límite, espacios en blanco exactos | Necesitas el texto tal como está almacenado — saltos de línea y espacios internos intactos. Prefiérelo con --format json. |
Un campo que excede el presupuesto se elide con un marcador que indica cuántos caracteres se ocultaron, p. ej. …(+2284). Ese marcador es tu señal de que existe más: si realmente lo necesitas, vuelve a recuperar solo ese campo para la fila específica con --detail full (longitud) o --format json --detail raw (espacios en blanco exactos) — consulta las salvaguardas.
Salvaguardas
Estos son los modos de fallo que este skill existe para prevenir:
- No satures tu contexto. Nunca hagas
SELECT *(o una columnatextancha) con--detail full/rawen muchas filas. En su lugar: escanea de forma estrecha con--detail compact/snippety una lista de columnas explícita y unLIMIT, identifica la fila que quieres, luego vuelve a recuperar solo el campo ancho de esa fila con unWHEREen su id. Un soloSELECT text FROM messages WHERE message_id = '…'es barato;SELECT * FROM messages --detail fulles una bomba de contexto. Cuando necesites exactamente los espacios en blanco (tablas markdown, bloques de código), usa--format json --detail raw. - Es de solo lectura — nunca intentes escrituras.
INSERT/UPDATE/DELETE/CREATEfallan todas (query failed: …). No reintentes una escritura con otra redacción; la superficie solo interroga. tool_inputes JSON heterogéneo. Sus claves varían por herramienta (Shelltienecommand,Readtienepath, …), por lo que untool_input->>'key'ingenuo puede lanzar un error de conversión. Extrae de forma segura filtrandotool_nameen una subconsulta/CTE primero y usando la función explícita:
stockroom query "SELECT json_extract_string(tool_input, '\$.command') AS cmd
FROM (SELECT tool_input FROM tool_calls WHERE tool_name = 'Shell') LIMIT 5"
Manejar errores sin aleteo
Cada fallo es un mensaje limpio en stderr + código de salida — léelo y toma la acción correspondiente, no hagas un bucle:
| Mensaje | Salida | Qué significa / siguiente acción |
|---|---|---|
error: empty query (…) | 2 | No se pasó SQL. Proporciona una sentencia. |
error: no warehouse found at … — run \stockroom ingest` first` | 1 | El almacén no se ha construido. Dile al usuario que ejecute stockroom ingest (o sr-initialize si la máquina nunca se configuró); no reintentes la consulta. |
query failed: … | 1 | SQL inválido, o se rechazó una escritura. Corrige la sentencia (lee el mensaje de DuckDB) — no vuelvas a ejecutar el mismo SQL. |
Qué hay en el almacén
Descubre el esquema en vivo primero — esto se mantiene correcto a medida que el esquema evoluciona:
stockroom query "SELECT table_name, column_name, data_type
FROM information_schema.columns ORDER BY table_name, ordinal_position"
Referencia rápida de las columnas importantes a partir de las migraciones 0001–0008 (confírmalo con la consulta de introspección anterior):
sessions— una fila por conversación.harness('cursor'|'claude'),session_id,project_id(slug del directorio del proyecto textual — la clave de agrupación; para los chats CLI de Cursor esto es el directorio hash de los chats),cwd(ruta real, puede serNULL),workspace_key(resumen de ruta entre herramientas, puede serNULL),entrypoint(procedencia superficial desde la herramienta cuando se conoce; puede serNULL— usaSELECT DISTINCT entrypointen lugar de adivinar literales),models(VARCHAR[], solo Cursor),title,git_branch,is_subagent,parent_session_id,started_at,ended_at,source_mtime(mtime de la transcripción fuente en la última ingesta), y cuatroBIGINTde token por sesión anulables (input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens— se llenan solo cuando una herramienta reporta uso a nivel de conversación; Claude/Cursor los dejan comoNULL). PK(harness, session_id).messages— una fila por turno.message_id = '{session_id}#{ordinal}'(uniforme entre herramientas),parent_id,ordinal,role('user'|'assistant'),text(completo; el pensamiento no se captura),model(por mensaje, solo Claude),ts,first_seen_at(cuando stockroom observó el mensaje por primera vez), y cuatroBIGINTde token:input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens(uso de mensaje de Claude; CursorNULL). PK(harness, session_id, message_id).session_token_usage— VISTA sobre sesiones + sumas de tokens de mensajes. Prefiere esta para resúmenes de conversación. Columnas:*_from_messages(SUM),*_native(columnas de sesión),*_total(COALESCE(native, from_messages)),token_grain('session'|'message'|'none'). Los totales son resúmenes del almacén de campos reportados, no facturas de proveedores. No hagas tambiénSUMde tokens de mensajes además de*_total.tool_calls— solo entradas de herramientas (nunca salidas).message_id(el turno que lo emitió),ordinal,tool_name,tool_input(JSONheterogéneo, almacenado completo — consulta la salvaguarda). PK(harness, session_id, message_id, ordinal).embeddings— vectores por fragmento para búsqueda semántica (owner_table,owner_id,chunk_index,embed_model,vector FLOAT[384]). Rara vez consultas esto directamente — usa el skillsr-semantic._sync_state— contabilidad de marcas de agua de ingesta; no interesante para consultar.
Una regla de identidad que vale la pena conocer antes de escribir una unión: únete siempre mediante el message_id / (harness, session_id) uniforme, nunca en las columnas de procedencia source_*. Un valor que solo existe en un nivel por herramienta es honestamente NULL para el otro (p. ej. messages.model es solo de Claude; sessions.models es solo de Cursor; la misma honestidad de doble nivel se aplica a los tokens).
Ejemplos prácticos
Todos verificados contra un almacén real.
# Qué herramientas están presentes? (tsv predeterminado)
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"
# Conteos de sesiones por herramienta, legible para un usuario:
stockroom query --format table "SELECT harness, count(*) AS n FROM sessions GROUP BY harness ORDER BY n DESC"
# Herramientas más ocupadas:
stockroom query "SELECT tool_name, count(*) AS calls FROM tool_calls GROUP BY tool_name ORDER BY calls DESC LIMIT 5"
# Escanea mensajes candidatos de forma barata, luego recupera el que quieras completo:
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>'"
# Salida estructurada para que un usuario/herramienta consuma:
stockroom query --format json "SELECT harness, session_id, title FROM sessions WHERE title IS NOT NULL LIMIT 5"
# Resúmenes de tokens por sesión (VISTA 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"
Más variantes (herramientas sin límite con ventana de actividad, resúmenes de tokens por día/herramienta, SQL de uso de habilidades por herramienta): consulta el Cookbook.
Cookbook
SQL inicial complicado: references/cookbook/. Abre el índice, luego solo la receta que necesites.
Transmitir a un humano
Tú eres el operador de la herramienta, no su pantalla. Ejecuta el SQL, lee el resultado y responde al usuario en lenguaje natural. No pegues tsv sin procesar a un humano a menos que haya pedido verlo. Cuando sí quiera la salida sin procesar — o un comando para ejecutar él mismo — entrégale una variante con --format table (para leer) o --format json (para procesar).
Entender el sistema
Para entender por qué estos contratos tienen este aspecto — el empaquetado, el contrato torch, solo-lectura-por-construcción, la doctrina de truncamiento, identidad/procedencia — lee el modelo de sistema compartido: ../sr-search/references/system-model.md.