checklist-qa
v0.8.2
Published
Conversational QA checklist interview engine, independent of any LLM provider.
Maintainers
Readme
checklist-qa
Transforma uma entrevista de QA (conduzida por qualquer LLM) numa experiência conversacional interativa, e entrega ao final um arquivo checklist.md pronto para uso — junto com o documento técnico e o changelog, tudo reunido numa única pasta anexos/.
O pacote não sabe nada sobre QA — todas as perguntas, regras de negócio e formato do checklist vivem inteiramente no prompt de entrevista (prompts/checklist-interview.md). O pacote apenas orquestra a conversa: envia o histórico ao seu provider de IA, guarda as respostas, detecta o fim da entrevista e disponibiliza o markdown resultante.
- Independente de modelo de IA: providers embutidos para OpenAI, Anthropic, Gemini, Azure OpenAI e Ollama; qualquer outro (DeepSeek, etc.) funciona implementando a mesma interface (
LLMProvider). - TypeScript com tipagem completa, ESM + CommonJS, Node 20+.
- Zero perguntas hardcoded no código — tudo vem do prompt.
Instalação
npm install checklist-qaCLI
O pacote inclui um CLI mínimo que roda a entrevista direto no terminal, usando o prompt embutido no pacote — útil para testar sem escrever código:
npx checklist-qa --provider anthropic| Flag | Descrição |
| --- | --- |
| --config <arquivo> | Arquivo de configuração JSON com valores padrão para as demais opções (ver Arquivo de configuração). |
| --provider <nome> | openai, anthropic, gemini, azure-openai, ollama, ou os apelidos local (= ollama), azure (= azure-openai) e nuvem (= provider de nuvem detectado automaticamente: anthropic > openai > gemini > azure-openai). Se omitido, usa a env var PROVIDER; se ela também não existir, é detectado a partir de ANTHROPIC_API_KEY > OPENAI_API_KEY_CHECKLIST > GEMINI_API_KEY > AZURE_OPENAI_API_KEY > Ollama local. |
| --model <nome> | Nome do modelo (padrão depende do provider). Para azure-openai, é o nome do deployment (ou use AZURE_OPENAI_DEPLOYMENT). |
| --prompt <arquivo> | Prompt de entrevista customizado (padrão: prompts/checklist-interview.md do pacote). |
| --output-dir <dir> | Diretório onde a pasta anexos/ (com o(s) checklist(s), doc.md e changelog.md) será criada (padrão: diretório atual). |
| --context-dir <dir> | Diretório onde procurar a pasta .diffai/ para usar como contexto adicional da entrevista (padrão: diretório atual). |
| --no-extension-guess | Desliga a heurística de classificar arquivos como Backend/Frontend pela extensão (.js = Backend, .ts = Frontend) quando não há indicação explícita no contexto. Use em projetos onde essa convenção não vale (ex.: backend em TypeScript). Ativa por padrão quando a flag é omitida. |
| --feature-doc-prompt <arquivo> | Prompt customizado para o documento técnico gerado em anexos/doc.md (padrão: prompts/feature-doc.md do pacote). |
| --no-feature-doc | Desativa a geração do documento técnico em anexos/doc.md. |
| --changelog-prompt <arquivo> | Prompt customizado para a entrada de changelog gerada em anexos/changelog.md (padrão: prompts/changelog.md do pacote). |
| --no-changelog | Desativa a geração da entrada de changelog em anexos/changelog.md. |
| --no-summary | Desativa a geração do resumo agregado anexos/00-resumo.md (ver Resumo agregado). |
| --no-resume | Descarta uma sessão salva anteriormente (.checklist-qa-session.json) e começa uma entrevista nova (ver Retomando uma entrevista interrompida). |
| --no-attach-reminder | Desliga o aviso final lembrando de anexar os arquivos gerados ao PR / Card. |
| -h, --help | Mostra a ajuda. |
Antes de iniciar a entrevista, o CLI consulta o npm para checar se a versão instalada é a mais recente publicada de checklist-qa e bloqueia a execução (com instruções de atualização) se não for. Se a consulta ao registro falhar (ex.: sem internet), o CLI apenas avisa e segue com a versão instalada, em vez de travar.
Contexto adicional (.diffai/)
Se o diretório de contexto (padrão: onde o CLI é executado, ou o valor de --context-dir) contiver uma pasta .diffai/, o CLI lê todos os arquivos dessa pasta recursivamente e anexa o conteúdo ao prompt de entrevista antes de iniciar a conversa. A IA analisa esse contexto antes de perguntar qualquer coisa e gera as perguntas dinamicamente a partir dele. docs/ (ex.: docs/analysis.md) não é lida: é apenas uma renderização em prosa do mesmo conteúdo de .diffai/analysis.json, então incluí-la também só duplicaria informação no prompt sem agregar nada novo.
Cada pergunta é apresentada já com uma resposta sugerida, pré-preenchida a partir do contexto e editável: no CLI, a sugestão abre no seu editor de texto ($VISUAL/$EDITOR, ou Notepad no Windows / nano nos demais), onde o desenvolvedor edita à vontade em várias linhas, salva e fecha para enviar. Quando o contexto não permite sugerir nada com segurança, o editor abre em branco. Se o contexto identificar mais de uma funcionalidade, elas são enumeradas (Funcionalidade 1, 2, ...) e tratadas separadamente na entrevista e no checklist. Ao final, o checklist.md combina o contexto (.diffai/) com as respostas confirmadas, destacando as mudanças importantes no sistema e os pontos de atenção.
Em entradas não interativas (stdin via pipe), o CLI faz fallback para uma leitura de linha única, sem abrir editor.
Se a pasta não existir, o comportamento é idêntico ao anterior: a entrevista é conduzida sem contexto extra, e nenhuma resposta é pré-preenchida.
Para usar essa mesma lógica fora do CLI, a classe ContextLoader está disponível publicamente:
import { ContextLoader } from "checklist-qa";
const context = await ContextLoader.loadFromDirectories(["./.diffai"]);Classificação Backend/Frontend gerada por código
Antes, a IA precisava interpretar sozinha os arquivos .diffai/analysis.*.json para decidir quais testes eram de backend e quais de frontend — e frequentemente errava, misturando os dois lados. Agora o CLI faz essa separação por código, de forma determinística, com a classe DiffaiClassifier: ele lê os arquivos analysis.backend-*.json e analysis.frontend-*.json, usa o lado declarado no nome de cada análise como autoridade (conferindo com o caminho de cada arquivo) e anexa ao prompt um bloco "Classificação Backend/Frontend determinada por código (AUTORITATIVA)" com as listas prontas de testes e arquivos de cada lado. A IA é instruída a copiar essa classificação exatamente como está — em vez de reinferi-la — e a não inventar arquivos que não constem do contexto. Nomes de teste "nus" (sem caminho), como transactionModel.test.js ou new-sale.component.spec.ts, também são classificados corretamente porque o lado vem do nome da análise de origem.
A flag --no-extension-guess também vale aqui: com ela, arquivos que não tenham lado determinável pelo nome da análise nem pelo caminho ficam marcados como indeterminado em vez de serem chutados pela extensão. Para usar a classificação fora do CLI:
import { DiffaiClassifier } from "checklist-qa";
const classification = await DiffaiClassifier.classifyDirectory("./.diffai");
const block = DiffaiClassifier.buildContextBlock(classification);Variáveis de ambiente usadas pelos providers embutidos:
PROVIDER— seleciona o provider sem passar--provider:local(= ollama) ounuvem(= provider de nuvem detectado automaticamente: anthropic > openai > gemini > azure-openai). Também aceitaanthropic|openai|gemini|azure-openai|ollama. A flag--providertem prioridade sobre esta variável.ANTHROPIC_API_KEY— necessária para o provider de nuvem anthropic.OPENAI_API_KEY_CHECKLIST— necessária para o provider de nuvem openai.GEMINI_API_KEY— necessária para o provider de nuvem gemini.AZURE_OPENAI_API_KEY/AZURE_OPENAI_ENDPOINT— necessárias para o provider de nuvem azure-openai (AZURE_OPENAI_ENDPOINTno formatohttps://SEU-RECURSO.openai.azure.com).AZURE_OPENAI_DEPLOYMENT— nome do deployment no Azure, usado quando--model/modelnão é passado.AZURE_OPENAI_API_VERSION— opcional para o provider de nuvem azure-openai (padrão2024-06-01).BASE_URL— opcional para o provider local (ollama): URL base do servidor. Tem prioridade sobreOLLAMA_BASE_URL.OLLAMA_BASE_URL— opcional para o provider local (ollama), padrãohttp://localhost:11434.
São os mesmos providers mínimos descritos em examples/providers — o CLI é só um ponto de entrada de terminal para o mesmo ChecklistQA. Use VISUAL ou EDITOR para escolher o editor em que as respostas são editadas (padrão: Notepad no Windows, nano nos demais).
O CLI carrega automaticamente um arquivo .env do diretório onde for executado (não do diretório do pacote), sem sobrescrever variáveis já exportadas no shell.
Arquivo de configuração (checklist-qa.config.json)
Para não repetir as mesmas flags a cada execução, o CLI aceita um arquivo checklist-qa.config.json no diretório atual (opcional — a ausência dele não é erro) com valores padrão para qualquer flag documentada acima:
{
"provider": "anthropic",
"contextDir": ".",
"noChangelog": true,
"noSummary": false
}Cada chave espelha exatamente uma flag (provider, model, prompt, outputDir, contextDir, noExtensionGuess, featureDocPrompt, noFeatureDoc, changelogPrompt, noChangelog, noSummary, noResume, noAttachReminder) — todas opcionais. Precedência: flag passada explicitamente na linha de comando > arquivo de configuração > padrão embutido.
Use --config <arquivo> para apontar para um arquivo com outro nome/local; nesse caso, o arquivo precisa existir (um --config explícito para um caminho inexistente é erro). Já o arquivo padrão (checklist-qa.config.json no diretório atual) é totalmente opcional — só falha alto se existir e estiver malformado (JSON inválido, não for um objeto, tiver uma chave não reconhecida ou uma chave com o tipo errado): um arquivo de configuração quebrado nunca é ignorado silenciosamente. Só JSON é aceito (sem .js/.ts de configuração), para não introduzir execução de código arbitrário.
Assim como as classes de providers inline do CLI, ConfigLoader é um detalhe de implementação do próprio CLI (src/ConfigLoader.ts) — não faz parte da API pública da biblioteca (ChecklistQA, que é consumida programaticamente, não lê arquivo de configuração nenhum).
Validação em CI (checklist-qa check)
npx checklist-qa checkDiferente do comando padrão, check não chama nenhuma IA — ele só valida que um checklist já foi gerado (localmente, antes de abrir a PR) e tem a estrutura que o prompt garante, e falha o build quando não estiver:
- Existe a pasta
anexos/com ao menos um arquivo.mdde funcionalidade (o00-resumo.mdagregado e os arquivosdoc.md/changelog.md, se existirem, não entram nessa validação — nenhum deles tem "Risco Geral" próprio). - Cada arquivo de funcionalidade tem a linha
**Risco Geral: <Baixo|Médio|Alto>**, a seção## Checklist de QA, a seção## Resumo Finale a seção### 7. Testes Identificados no Contextocom as subseções#### Backende#### Frontend.
| Flag | Descrição |
| --- | --- |
| --output-dir <dir> | Diretório onde procurar a pasta anexos/ (padrão: diretório atual). |
| -h, --help | Mostra a ajuda do subcomando. |
Termina com código de saída 0 (tudo OK) ou 1 (faltou o checklist ou ele está incompleto) — pensado para um step de CI (ex.: Bitbucket Pipelines) que bloqueia o merge:
- step:
name: Verifica checklist de QA
script:
- npx checklist-qa checkPara usar a mesma validação de fora do CLI, a classe ChecklistValidator está disponível publicamente:
import { ChecklistValidator } from "checklist-qa";
const report = await ChecklistValidator.checkDirectory("./");Uso rápido
import { ChecklistQA, PromptLoader } from "checklist-qa";
import { myProvider } from "./my-provider.js";
const prompt = await PromptLoader.loadFromFile("./node_modules/checklist-qa/prompts/checklist-interview.md");
const qa = new ChecklistQA({
provider: myProvider,
prompt,
});
let question = await qa.start();
while (!qa.isFinished()) {
console.log(question);
// Resposta sugerida a partir do contexto (.diffai): apresente-a já preenchida e editável.
const answer = await getAnswerFromUser(qa.suggestedAnswer()); // sua UI, CLI, etc.
question = await qa.answer(answer);
}
console.log(qa.getMarkdown()); // conteúdo final do checklist.md
await qa.download(); // grava anexos/checklist.md em disco (Node) ou dispara o download (browser)Você também pode copiar prompts/checklist-interview.md para o seu próprio projeto e ajustá-lo, desde que mantenha o marcador <QA_CHECKLIST_READY> (veja Como funciona a entrevista).
Configuração
interface ChecklistQAOptions {
provider: LLMProvider; // obrigatório — seu adaptador de IA
prompt: string; // obrigatório — texto do prompt de entrevista
outputFileName?: string; // padrão: "checklist.md"
outputDirName?: string; // padrão: "anexos" — pasta única que reúne checklist(s), doc.md e changelog.md
autoDownload?: boolean; // padrão: false — baixa/gera o arquivo automaticamente ao concluir
marker?: string; // padrão: "<QA_CHECKLIST_READY>"
suggestionMarker?: string; // padrão: "<RESPOSTA_SUGERIDA>" — separa a pergunta da resposta pré-preenchida
progressMarker?: string; // padrão: "<PROGRESSO>" — precede a estimativa "pergunta atual/total"
sessionId?: string; // padrão: gerado automaticamente (crypto.randomUUID)
}API
| Método | Descrição |
| --- | --- |
| qa.start() | Envia o prompt ao provider e retorna a primeira pergunta. |
| qa.answer(texto) | Envia a resposta do usuário e retorna a próxima pergunta ("" quando a entrevista termina). |
| qa.question() | Última pergunta feita pela IA. |
| qa.suggestedAnswer() | Resposta sugerida (pré-preenchida a partir do contexto) para a última pergunta ("" quando não há). |
| qa.progress() | { current, total } autorreportado pela IA para a última pergunta (undefined quando não informado). total é uma estimativa que pode mudar entre perguntas. |
| qa.isFinished() | true quando o marcador de conclusão foi detectado. |
| qa.getMarkdown() | Conteúdo do checklist.md (undefined até a entrevista terminar). |
| qa.getHistory() | Histórico completo da conversa (nunca perde mensagens). |
| qa.download(outputDir?) | Grava o arquivo em disco (Node) ou dispara o download (browser). |
Criando um Provider
A única coisa que o pacote exige de um provider é:
interface LLMProvider {
chat(messages: Message[]): Promise<Message>;
}
type Role = "system" | "user" | "assistant";
interface Message {
role: Role;
content: string;
}Toda vez que uma pergunta precisa ser feita, o InterviewEngine chama provider.chat(historicoCompleto) e espera de volta uma única mensagem assistant. Você decide como essa chamada conversa com a IA por trás.
Exemplos completos ficam em examples/providers: OpenAI, Anthropic, Gemini, Azure OpenAI e Ollama. Um provider mínimo para qualquer serviço com API compatível:
import type { LLMProvider, Message } from "checklist-qa";
export class MyProvider implements LLMProvider {
async chat(messages: Message[]): Promise<Message> {
const content = await callMyLLM(messages);
return { role: "assistant", content };
}
}Como funciona a entrevista
qa.start()envia o prompt de entrevista (mensagemsystem) para oLLMProvidere recebe a primeira pergunta.A cada
qa.answer(resposta), a resposta do usuário é adicionada ao histórico e o histórico completo é reenviado ao provider — o pacote nunca trunca ou reescreve mensagens anteriores.O pacote nunca mostra mais de uma pergunta por vez — isso é responsabilidade do prompt, que já instrui a IA a perguntar um item por mensagem.
Cada pergunta pode vir acompanhada de uma resposta sugerida (derivada do contexto), separada da pergunta pelo marcador
<RESPOSTA_SUGERIDA>. OInterviewEnginesepara a pergunta (antes do marcador) da sugestão (depois dele) e expõe a sugestão viaqa.suggestedAnswer()— apresente-a já preenchida e editável na sua UI. Se a pergunta não tiver marcador,qa.suggestedAnswer()retorna"". 4b. Cada pergunta também pode vir com uma estimativa de progresso, no formato<PROGRESSO>\npergunta_atual/total_estimado. Como a entrevista é dinâmica (sem lista fixa de perguntas), ototalé a própria IA quem estima e pode revisar a cada pergunta. OInterviewEngineexpõe isso viaqa.progress()(undefinedquando a pergunta não trouxe a estimativa).Quando a IA decide que a entrevista terminou, ela responde exatamente com o marcador:
<QA_CHECKLIST_READY>seguido, na mesma mensagem, do conteúdo completo do
checklist.mdem Markdown.O
InterviewEnginedetecta o marcador, separa qualquer texto antes dele (última "pergunta", normalmente vazia) do conteúdo após ele (o Markdown), marca a sessão como finalizada e armazena o Markdown.A partir daí,
qa.isFinished()retornatrueeqa.getMarkdown()/qa.download()ficam disponíveis.
Use as opções marker, suggestionMarker e progressMarker em ChecklistQAOptions caso seu prompt use marcadores diferentes.
Retomando uma entrevista interrompida
No CLI, o progresso é salvo automaticamente a cada resposta em .checklist-qa-session.json (no diretório atual) e retomado sozinho na próxima execução, caso o processo seja interrompido (fechar o terminal, crash, Ctrl+C) no meio de uma entrevista longa — sem precisar de nenhuma flag. Ao terminar a entrevista com sucesso, esse arquivo é apagado, então uma execução concluída nunca é confundida com uma sessão retomável.
Se a versão do checklist-qa ou o prompt de entrevista mudou desde que a sessão foi salva, o CLI avisa (nunca recusa nem apaga silenciosamente) e continua retomando mesmo assim — um bloqueio deixaria você sem saída a não ser apagar o arquivo manualmente. Use --no-resume para descartar uma sessão salva e começar do zero quando isso acontecer, ou sempre que preferir recomeçar.
Só é possível ter uma entrevista em andamento por diretório (o nome do arquivo é fixo). Se você usa controle de versão, adicione .checklist-qa-session.json ao seu .gitignore — é um arquivo de rascunho local, não um artefato do checklist.
Para implementar a mesma persistência na sua própria UI (fora do CLI), use qa.serialize() / ChecklistQA.restore():
import { ChecklistQA } from "checklist-qa";
// Salvar (a qualquer momento, tipicamente após start()/answer()):
const snapshot = qa.serialize();
await salvarEmAlgumLugar(JSON.stringify(snapshot));
// Retomar depois, com um provider (e demais opções) fornecidos de novo:
const snapshot2 = JSON.parse(await lerDeAlgumLugar());
const qa2 = ChecklistQA.restore(snapshot2, { provider, prompt });options.provider/prompt/featureDocPrompt/changelogPrompt nunca fazem parte do snapshot (não são serializáveis) — forneça-os de novo em restore(). options.sessionId é ignorado: o id real vem do próprio snapshot.
Geração e download do Markdown
Todo arquivo gerado — checklist(s), resumo agregado, documento técnico e changelog — é sempre gravado dentro de uma única pasta compartilhada, outputDirName (padrão anexos/), nunca solto na raiz de outputDir.
qa.getMarkdown()retorna a string do Markdown assim que a entrevista termina.qa.download(outputDir?):- Uma única funcionalidade: grava
checklist.md(ou ooutputFileNameconfigurado) dentro deanexos/viafs/promises(Node), opcionalmente dentro deoutputDir, e retorna o caminho gravado. No browser, cria umBlobe dispara o download (sem depender de nenhum framework), com o nome da pasta prefixado ao arquivo (anexos-checklist.md). - Mais de uma funcionalidade: grava um arquivo por funcionalidade (
1-<slug>.md,2-<slug>.md, ...) dentro dessa mesma pastaanexos/(ou ooutputDirNameconfigurado), e retorna a lista de caminhos gravados. No browser, cada arquivo é baixado separadamente com o nome da pasta prefixado (anexos-1-<slug>.md).
- Uma única funcionalidade: grava
qa.downloadFeatureDocs(outputDir?)eqa.downloadChangelogs(outputDir?)gravam, respectivamente,doc.mdechangelog.mddentro dessa mesma pastaanexos/— cada um sobrescreve apenas o seu próprio arquivo fixo, sem afetar o(s) checklist(s) nem o outro documento já gravados ali.- Configure
autoDownload: truepara que o download aconteça automaticamente assim queqa.isFinished()se tornartrue, sem precisar chamarqa.download()manualmente.
Resumo agregado (multi-funcionalidade)
Quando a entrevista cobre mais de uma funcionalidade, download() também grava anexos/00-resumo.md — um arquivo puramente derivado por código (sem chamada extra à IA) que consolida, numa tabela, o **Risco Geral: <Baixo|Médio|Alto>** de cada funcionalidade (linha que o prompt sempre gera, logo abaixo do título) e calcula um risco geral do PR (o mais alto entre as funcionalidades). O nome 00- garante que esse arquivo sempre apareça primeiro na pasta, antes dos arquivos 1-..., 2-... de cada funcionalidade. Quando a entrevista cobre só uma funcionalidade, não há nada para agregar e esse arquivo não é gerado.
Se alguma funcionalidade não tiver uma linha de risco reconhecível (formato inesperado), ela aparece na tabela como "Risco não determinado" em vez de ser omitida ou de herdar um risco arbitrário — e fica de fora do cálculo do risco geral do PR.
Use noSummary: true em ChecklistQAOptions (ou --no-summary no CLI) para desativar essa geração.
Para usar a mesma lógica de fora do ChecklistQA, a classe ChecklistSummary está disponível publicamente:
import { ChecklistSummary } from "checklist-qa";
const { risk, justification } = ChecklistSummary.parseRisk(markdown);
const overall = ChecklistSummary.aggregateRisk([risk1, risk2]);FAQ
Preciso reescrever as perguntas da entrevista no código?
Não. Todas as perguntas, regras e o formato do checklist final vivem em prompts/checklist-interview.md. O pacote só orquestra o fluxo.
Posso usar qualquer LLM?
Sim. Implemente LLMProvider (um único método, chat) para o serviço que preferir. O pacote nunca importa um SDK de IA específico.
O que acontece se a IA esquecer o marcador <QA_CHECKLIST_READY>?
A entrevista nunca é marcada como concluída e qa.getMarkdown()/qa.download() continuam indisponíveis — ajuste o prompt ou o marker configurado se isso acontecer com frequência.
Funciona em CommonJS? Sim, o pacote publica builds ESM e CommonJS com tipos para ambos.
Como troco de provider no meio do desenvolvimento?
Basta passar uma instância diferente em provider ao criar o ChecklistQA — nada mais no pacote precisa mudar.
