sr-query
sr-query executa SQL somente leitura no warehouse do stockroom — o banco de dados DuckDB de arquivo único do seu histórico capturado do Cursor + Claude Code. A superfície é somente leitura por construção: DuckDB rejeita qualquer tentativa de escrita, então você não pode corromper nada ao consultar.
Quando usar o sr-query
Recorra ao sr-query quando a pergunta tiver um formato conhecido e a resposta for uma consulta exata ou estruturada sobre campos que você pode nomear:
- Uma linha específica por id (
WHERE message_id = '…',WHERE session_id = '…'). - Filtros, contagens e agregações (
COUNT(*),GROUP BY harness,WHERE harness = 'claude', somas de tokens, intervalos de datas). - Agregações de tokens por sessão via VIEW
session_token_usage(prefira isto aSUMmanual emmessages). - Joins entre
sessions/messages/tool_calls. - "Quantas", "quais sessões", "liste as chamadas de ferramenta", "quais modelos" — tudo que você possa expressar como SQL sobre colunas nomeadas.
Não use sr-query para recuperação baseada em significado ("encontre conversas sobre testes instáveis"). SQL ILIKE é uma correspondência literal de substring, não busca semântica — para significado, use a skill sr-semantic. Quando você não tiver certeza de qual é a correta, esse julgamento pertence à skill sr-search.
Como invocar o motor
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"
Você também pode passar SQL pela entrada padrão com -:
echo "SELECT count(*) FROM messages" | stockroom query -
Se command -v stockroom falhar, a máquina não está configurada: diga ao usuário para executar a skill sr-initialize, e não tente nenhuma outra invocação.
Disciplina de saída: --format e --detail
Os padrões já são seguros para um agente — uma chamada simples dá saída limitada e analisável — então recorra às flags apenas quando a situação exigir.
--format — formato da saída, padrão tsv
| Valor | Formato | Use quando |
|---|---|---|
tsv (padrão) | Linha de cabeçalho + linhas separadas por tabulação, sem contagem final | Padrão. Amigável para fluxo e para pipes Unix (cut, awk, wc -l). |
json | Um único objeto {"columns": [...], "rows": [...]} | Um usuário pede saída estruturada/de máquina, ou você quer passar para jq. |
table | ASCII alinhado por colunas com rodapé (N rows) | Um usuário pede algo legível / um comando copiável para olhar. |
Use o padrão tsv. Ofereça --format table ou --format json quando o usuário pedir saída legível ou estruturada — "estes outros formatos existem se você quiser."
--detail — largura por campo, padrão snippet
Campos de string longos (notadamente messages.text e tool_calls.tool_input) são truncados no momento da leitura para que uma coluna grande não inunde seu contexto. O truncamento é apenas de exibição — o texto completo sempre pode ser recuperado.
| Valor | Orçamento por campo | Use quando |
|---|---|---|
compact | ~40 caracteres | Escaneando muitas linhas candidatas de forma barata antes de escolher uma. |
snippet (padrão) | ~120 caracteres | Padrão. Suficiente para reconhecer uma linha sem despejá-la. |
full | ilimitado, linha única | Você precisa do comprimento total do campo, e a quebra de linha única é aceitável (tabelas/TSV permanecem seguros). |
raw | ilimitado, espaçamento exato | Você precisa do texto como armazenado — quebras de linha e espaços internos intactos. Prefira com --format json. |
Um campo acima do orçamento é omitido com um marcador reportando quantos caracteres foram ocultados, ex.: …(+2284). Esse marcador é seu sinal de que mais existe: se você realmente precisar, busque novamente apenas aquele campo para a linha específica com --detail full (comprimento) ou --format json --detail raw (espaçamento exato) — veja as proteções.
Proteções
Estes são os modos de falha que esta skill existe para prevenir:
- Não exploda seu contexto. Nunca use
SELECT *(ou uma colunatextlarga) com--detail full/rawem muitas linhas. Em vez disso: escaneie de forma estreita com--detail compact/snippetcom uma lista explícita de colunas e umLIMIT, identifique a linha desejada, então busque novamente apenas o campo largo daquela linha com umWHEREem seu id. Um únicoSELECT text FROM messages WHERE message_id = '…'é barato;SELECT * FROM messages --detail fullé uma bomba de contexto. Quando você precisar de espaçamento exato (tabelas markdown, blocos de código), use--format json --detail raw. - É somente leitura — nunca tente escritas.
INSERT/UPDATE/DELETE/CREATEtudo falha (query failed: …). Não tente novamente uma escrita com uma formulação diferente; a superfície apenas interroga. tool_inputé JSON heterogêneo. Suas chaves variam por ferramenta (Shelltemcommand,Readtempath, …), então umtool_input->>'key'ingênuo pode gerar um erro de cast. Extraia com segurança filtrandotool_nameem uma subconsulta/CTE primeiro e usando a função 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"
Trate erros sem repetição
Cada falha é uma mensagem limpa no stderr + código de saída — leia e tome a ação correspondente, não faça loop:
| Mensagem | Saída | O que significa / próxima ação |
|---|---|---|
error: empty query (…) | 2 | Nenhum SQL foi passado. Forneça uma instrução. |
error: no warehouse found at … — run \stockroom ingest` first` | 1 | O warehouse não foi construído. Diga ao usuário para executar stockroom ingest (ou sr-initialize se a máquina nunca foi configurada); não tente a consulta novamente. |
query failed: … | 1 | SQL inválido, ou uma escrita foi rejeitada. Corrija a instrução (leia a mensagem do DuckDB) — não execute o mesmo SQL novamente. |
O que está no warehouse
Descubra o esquema ativo primeiro — isso permanece correto à medida que o esquema evolui:
stockroom query "SELECT table_name, column_name, data_type
FROM information_schema.columns ORDER BY table_name, ordinal_position"
Referência rápida das colunas principais a partir das migrações 0001–0008 (confirme com a consulta de introspecção acima):
sessions— uma linha por conversa.harness('cursor'|'claude'),session_id,project_id(slug do diretório do projeto — a chave de agrupamento; para chats do Cursor CLI este é o diretório hash dos chats),cwd(caminho real, pode serNULL),workspace_key(agregação de caminho entre harnesses, pode serNULL),entrypoint(proveniência da superfície do harness quando conhecido; pode serNULL— useSELECT DISTINCT entrypointem vez de adivinhar literais),models(VARCHAR[], apenas Cursor),title,git_branch,is_subagent,parent_session_id,started_at,ended_at,source_mtime(mtime do transcripto fonte na última ingestão), e quatroBIGINTs de token por sessão anuláveis (input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens— preenchidos apenas quando um harness reporta uso no nível da conversa; Claude/Cursor os deixamNULL). PK(harness, session_id).messages— uma linha por turno.message_id = '{session_id}#{ordinal}'(uniforme entre harnesses),parent_id,ordinal,role('user'|'assistant'),text(todo; pensamento não é capturado),model(por mensagem, apenas Claude),ts,first_seen_at(quando o stockroom observou a mensagem pela primeira vez), e quatroBIGINTs de token:input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens(uso de mensagem do Claude; CursorNULL). PK(harness, session_id, message_id).session_token_usage— VIEW sobre sessões + somas de tokens de mensagens. Prefira esta para agregações de conversa. Colunas:*_from_messages(SUM),*_native(colunas da sessão),*_total(COALESCE(native, from_messages)),token_grain('session'|'message'|'none'). Os totais são agregações do warehouse dos campos reportados, não faturas de fornecedores. Não faça tambémSUMde tokens de mensagem além de*_total.tool_calls— apenas entradas de ferramenta (nunca saídas).message_id(o turno que a emitiu),ordinal,tool_name,tool_input(JSON heterogêneo, armazenado completo — veja a proteção). PK(harness, session_id, message_id, ordinal).embeddings— vetores por chunk para busca semântica (owner_table,owner_id,chunk_index,embed_model,vector FLOAT[384]). Raramente você consulta isto diretamente — use a skillsr-semantic._sync_state— contabilidade de marcas d'água de ingestão; não é interessante para consultar.
Uma regra de identidade que vale a pena saber antes de escrever um join: sempre junte pelo message_id / (harness, session_id) uniforme, nunca pelas colunas de proveniência source_*. Um valor que existe apenas em um grão por harness é honestamente NULL para o outro (ex.: messages.model é apenas Claude; sessions.models é apenas Cursor; a mesma honestidade de grão duplo se aplica a tokens).
Exemplos práticos
Todos verificados contra um warehouse real.
# Quais harnesses estão presentes? (tsv padrão)
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"
# Contagens de sessões por harness, legível para um usuário:
stockroom query --format table "SELECT harness, count(*) AS n FROM sessions GROUP BY harness ORDER BY n DESC"
# Ferramentas mais usadas:
stockroom query "SELECT tool_name, count(*) AS calls FROM tool_calls GROUP BY tool_name ORDER BY calls DESC LIMIT 5"
# Escaneie mensagens candidatas de forma barata, depois busque novamente a desejada por 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>'"
# Saída estruturada para um usuário/ferramenta consumir:
stockroom query --format json "SELECT harness, session_id, title FROM sessions WHERE title IS NOT NULL LIMIT 5"
# Agregações de tokens por sessão (VIEW 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"
Mais variantes (ferramentas ilimitadas com janela de atividade, agregações de tokens por dia/harness, SQL de uso de skill por harness): veja o Cookbook.
Cookbook
SQL inicial complicado: references/cookbook/. Abra o índice e depois apenas a receita que você precisa.
Relato a um humano
Você é o operador da ferramenta, não sua tela. Execute o SQL, leia o resultado e responda ao usuário em linguagem natural. Não cole TSV bruto para um humano a menos que ele tenha pedido para vê-lo. Quando ele quiser a saída bruta — ou um comando para executar ele mesmo — forneça uma variante --format table (para ler) ou --format json (para processar).
Entendendo o sistema
Para entender por que esses contratos são como são — o empacotamento, o contrato tocha, somente leitura por construção, a doutrina de truncamento, identidade/proveniência — leia o modelo de sistema compartilhado: ../sr-search/references/system-model.md.