@trywhet/cli
v0.1.0
Published
Whet — static linter for AI system prompts. Detects patterns that degrade agent behavior and generates a rewrite meta-prompt for correction. Supports PT/EN/ES.
Downloads
81
Maintainers
Readme
Whet
Pessoas não são boas em prompt. Muitas vezes, sequer querem escrever prompts elaborados. Mas o comportamento da IA depende diretamente de como você a orienta — e a diferença entre uma orientação boa e uma ruim pode ser a diferença entre um resultado útil e uma experiência frustrante.
Poucas pessoas acumularam experiência suficiente para saber quais orientações funcionam e quais não funcionam. Whet destila essa experiência em algo que qualquer pessoa — ou agente — pode usar.
O que o sistema faz
O usuário cola seu prompt. O sistema detecta padrões que, pela experiência, degradam o comportamento de agentes de IA. O resultado é um prompt reescrito, entregue direto no navegador.
A análise é 100% local (client-side, offline). A reescrita é opcional e acontece em um clique: o sistema manda a instrução de correção para uma das IAs gratuitas parceiras — Gemini, Mistral ou Llama via Groq — com rotação LRU entre elas para amortizar os free tiers, e devolve o prompt reescrito com atribuição explícita ("Reescrito por Gemini 2.5 Flash") e delta de score (86 → 100, com count-up animado).
Por baixo, o que alimenta a reescrita é o meta-prompt de reescrita — uma instrução de correção autocontida que inclui o prompt original, lista as adequações sugeridas, e instrui o destinatário a preservar o propósito original de cada instrução, descartando o que não fizer sentido. Ele não some: fica acessível a um clique num acordeão "Ver instrução de correção", pra quem quer auditar o porquê ou usar o fluxo manual (copiar e colar numa IA de preferência, inclusive modelos locais).
Três caminhos, um mesmo core:
- One-click (default) — clique em "Reescrever com IA", o rotador escolhe uma IA gratuita disponível, o prompt reescrito aparece com animação materializando o container
- Manual (fallback automático) — se todas as IAs gratuitas estão exauridas, ou se o usuário preferir, o painel degrada pra mostrar a instrução de correção copiável
- Educativo (sempre disponível) — diagnósticos, sugestões, dicas de reformulação e razões de cada detecção ficam visíveis sob o painel principal, independente do caminho escolhido
O que a experiência mostra sobre instruções
Estes padrões vêm de situações reais, não de teoria. Se não há uma história real por trás de uma regra — um momento onde a IA se comportou de forma inadequada ou surpreendentemente boa — essa regra não deveria existir. Isso vale para as instruções que o linter analisa e para as regras do próprio linter.
Uma instrução ruim é pior do que nenhuma instrução
Um prompt mal escrito não é neutro — o agente gasta esforço tentando cumprir regras em vez de atender ao propósito real de quem está usando. Instruções que restringem demais podem acabar sendo piores do que nenhuma instrução — porque a IA gasta energia avaliando regras em vez de ser produtiva.
Orientar sobre o que não funciona vale mais do que dizer o que fazer
Instruções positivas ("seja útil", "responda com clareza") quase sempre repetem o que o modelo já faz sozinho. As instruções que mudam comportamento de verdade são as que sinalizam armadilhas — o que tende a dar errado e por quê.
Tom imperativo gera paralisia — e conflitos
Instruções rígidas ("SEMPRE faça X", "NUNCA faça Y", "É OBRIGATÓRIO", "É PROIBIDO") produzem agentes excessivamente cautelosos. Além de paralisia, geram conflitos — quando várias instruções imperativas competem, o agente não sabe qual priorizar.
Instruções que sugerem produzem agentes que entendem o propósito e têm margem para se adaptar ao contexto. A boa instrução define propósito, não procedimento.
Reforçar o que o modelo já faz é desperdício — e pode causar dano
Se o modelo já tem um comportamento por padrão, incluí-lo nas instruções não é neutro. Além de desperdiçar espaço de atenção, pode tornar o agente mais restritivo do que o desejado. Cada instrução precisa genuinamente mudar algo que sem ela não seria diferente.
Menos instruções, mais eficácia
Cada instrução adicional compete por atenção do agente, e o excesso causa paralisia ou comportamento inconsistente. Na prática, 3 a 5 instruções por bloco e no máximo 10 no total tendem a ser os limites onde a eficácia se mantém.
Restrições categóricas são diferentes de orientações comportamentais
"Use TypeScript" ou "responda em JSON" são restrições de ferramenta ou formato — binárias, legítimas, que não se beneficiam de tom sugestivo. "Sempre cite a legislação" é uma orientação comportamental — faz sentido reformular para que o agente entenda o propósito. O linter distingue as duas: restrições categóricas podem ser mantidas como estão.
Domínio importa — nem todo imperativo é igual
Em contextos onde erro gera risco real (saúde, direito, finanças), certas instruções imperativas podem ser mais justificáveis do que em outros domínios. O meta-prompt de reescrita sinaliza o domínio detectado e orienta a IA destinatária a preservar proteções que existem por razões de segurança ou compliance, reformulando para que o agente entenda o porquê em vez de apenas obedecer.
O que o linter detecta
Cada regra detecta um padrão e gera orientações que alimentam o meta-prompt de reescrita.
| Regra | Situação que endereça |
|---|---|
| redundant-default | Instruções que repetem comportamento que o modelo já tem, desperdiçando atenção e podendo torná-lo mais restritivo |
| imperative-overload | Excesso de linguagem imperativa que tende a gerar agentes travados, cautelosos e com conflitos entre regras |
| cognitive-overload | Muitas instruções competindo por atenção, além dos limites onde a eficácia se mantém (com tolerância maior para prompts estruturados — listas numeradas ou prosa com seções rotuladas em negrito) |
| contradiction | Instruções que se opõem dentro do mesmo prompt — conciso vs exaustivo, formal vs informal, idioma fixo vs idioma do usuário — forçando o agente a adivinhar qual priorizar |
| vague-instruction | Instruções genéricas demais para ter efeito real — o modelo interpreta como quiser |
| redundant-repetition | Mesma ideia repetida com palavras diferentes, inflando o prompt sem agregar |
| command-over-question | Comandos diretos e negações sem explicar o propósito — o agente adere mecanicamente em vez de entender a intenção |
| threat-framing | Ameaças condicionais e framing por medo que tendem a gerar cautela paralisante em vez de orientar o modelo |
| role-inflation | Inflação de credenciais no papel atribuído ao modelo (o melhor do mundo, 25 anos de experiência, prêmios internacionais) que não muda o comportamento |
| conditional-reward | Promessas de gorjeta, recompensa ou avaliação positiva que são vazias para um modelo e deslocam atenção do propósito real |
| tone-domain-mismatch | Tom casual/informal pedido explicitamente em domínio sensível (jurídico, saúde, finanças, contábil) onde a formalidade tem função de compliance, risco ou responsabilidade |
| unresolved-reference | Instruções que remetem a artefatos externos não fornecidos no contexto do modelo (documentos anexos, templates, apêndices) que degradam silenciosamente |
Critérios para existência de uma regra
O linter não é dogmático sobre suas próprias regras — são orientações, não fórmulas. Mas cada regra precisa atender a estes critérios:
- Não duplica comportamento que o modelo já tem por padrão
- Tem base em experiência real, não em teoria
- Genuinamente muda algo no comportamento
- Funciona por si só, sem depender de outras regras
- Tensões com outras regras, quando existem, são declaradas e genuínas
Arquitetura
O sistema tem três camadas que se comunicam numa direção só:
UI (o que o usuário vê)
└→ Core (a inteligência — analisa o texto)
└→ Regras (cada uma detecta um problema específico)A camada de cima conhece a de baixo, mas a de baixo não sabe que a de cima existe. O core não sabe que está num site. As regras não sabem que existe um core coordenando. Essa independência é o que permite reutilizar o core em outros contextos depois — CLI, extensão, API — sem reescrever.
Core
Recebe texto, devolve análise. Não tem opinião sobre como o resultado vai ser exibido.
src/core/
├── models.ts Vocabulário do sistema (Severity, Diagnostic, Rule, AnalysisResult, splitIntoStatements)
├── analyzer.ts Coordenador — passa o texto por todas as regras, calcula score, gera output + positive traits
├── renderer.ts Gera a instrução de correção (texto copiável) + detecção de domínio + exemplos concretos + positive traits bilíngues
├── rule-meta.ts Metadados das regras (títulos, intros bilíngues pt/en) + utilitário groupByRule + getRuleMeta(lang)
├── i18n.ts Dicionário de strings da UI (pt/en) — cobre labels, placeholders, mensagens, score labels, severity labels e error boundary
└── rules/
├── index.ts Registro das regras disponíveis
├── redundant-default.ts Instruções que repetem comportamento padrão (padrões PT/EN/ES)
├── redundant-repetition.ts Mesma ideia repetida com palavras diferentes (padrões PT/EN/ES)
├── imperative-overload.ts Excesso de linguagem imperativa (gera tip; exclude estrutural cobre singular e plural; padrões PT/EN/ES)
├── cognitive-overload.ts Muitas instruções ou texto longo demais (conta sentenças em blocos contínuos; prompts estruturados — listas numeradas ou prosa com ≥3 seções rotuladas em negrito — têm limite maior; padrões PT/EN/ES)
├── vague-instruction.ts Instruções genéricas demais (padrões PT/EN/ES)
├── command-over-question.ts Comandos diretos e negações sem propósito (gera tip; padrões PT/EN/ES)
├── threat-framing.ts Ameaças condicionais e framing por medo (gera tip; padrões PT/EN/ES)
├── role-inflation.ts Inflação de credenciais no papel atribuído ao modelo (gera tip; padrões PT/EN/ES)
├── conditional-reward.ts Promessas de recompensa condicional ao modelo (gera tip; padrões PT/EN/ES)
├── tone-domain-mismatch.ts Tom casual pedido em domínio sensível (jurídico, saúde, finanças, contábil; padrões PT/EN/ES)
├── contradiction.ts Pares de instruções opostas no mesmo prompt (10 eixos: concisão vs exaustividade, formal vs informal, exemplos mínimos vs exaustivos, idioma fixo vs idioma do usuário, fontes vs sem-fontes, criatividade vs rigidez, idioma do usuário vs idioma fixo, não-inventar vs responder-com-confiança, velocidade vs profundidade, concordância vs confronto)
└── unresolved-reference.ts Referências a artefatos externos não fornecidos (documentos anexos, templates, apêndices) — padrões PT/EN/ESmodels.ts define o vocabulário e inclui splitIntoStatements() — utilitário que divide texto em instruções individuais, suportando tanto uma instrução por linha quanto blocos de texto contínuo (split por sentença). Todas as regras usam esse utilitário. Cada Diagnostic inclui um campo highlight com a palavra/trecho específico que disparou a regra. AnalysisResult inclui positiveTraits — pontos positivos do prompt, exibidos quando não há problemas detectados.
renderer.ts gera a instrução de correção com:
- Detecção automática de idioma (pt/en/es) — o meta-prompt de reescrita é gerado no mesmo idioma do prompt analisado
- Detecção automática de domínio (veterinária, nutrição, saúde, contabilidade/tributário, direito, marketing, finanças, energia/petróleo, real estate/imobiliário, agricultura/agronegócio, cinema/audiovisual, educação, gestão, engenharia civil, arquitetura/urbanismo, jornalismo, meio ambiente, logística/supply chain, design/UX, programação)
- Exemplos concretos por adequação, extraídos do próprio prompt analisado
- Ressalva para restrições categóricas (ferramenta/formato) no closing
- Instrução de formato: a IA destinatária deve devolver texto corrido, sem markdown, pronto para usar
Regras que geram tip oferecem uma orientação estática de reformulação, alinhada à filosofia do sistema, exibida diretamente na UI.
UI
src/app/
├── page.tsx Componente principal (Home) — state management, encode/decode URL, header, toggle de idioma com localStorage, skip-to-content
├── layout.tsx Layout raiz — metadados, JSON-LD structured data, wrapper global
├── not-found.tsx Página 404 customizada bilíngue
├── opengraph-image.tsx Geração dinâmica da imagem OG da home
├── twitter-image.tsx Geração dinâmica da imagem Twitter card
├── globals.css Estilos globais, animações (blur-to-sharp, section-reveal), glassmorphism
├── privacy/ Página de privacidade (rotação, limites, política de cada provider)
├── whet-benchmark/ Página pública do Whet Benchmark (abas Corpus e Ao vivo)
└── blog/ Blog bilíngue — listagem, posts, layout, RSS, OG imagessrc/components/
├── SiteHeader.tsx Header global bilíngue (Whet wordmark, toggle PT/EN, nav para benchmark e blog)
├── SiteFooter.tsx Footer global (contato [email protected], links internos, atribuição)
├── LandingView.tsx Tela inicial — hero com blur-to-sharp, radial glow, value props, input
├── AnalysisView.tsx View de resultado — split view, prompt anotado (dissolve cross-panel), diagnósticos
├── RewritePanel.tsx Painel de reescrita one-click (idle/loading/done/error) + celebração de score
├── ScoreRing.tsx Score como anel SVG animado (CSS transition, tooltip explicativo)
├── JsonTab.tsx Dados estruturados da análise (JSON copiável, inclui positiveTraits)
├── PromptInput.tsx Editor com gutter de linhas e indicadores de severidade
├── ExamplePrompts.tsx Quick-picks + painel expansível com exemplos PT/EN, triggers bilíngues
├── BenchmarkPodium.tsx Podium visual do ranking ao vivo, exibido na landing
├── CopyEmailButton.tsx Botão de copiar e-mail de contato (usado no footer)
├── CopyLinkButton.tsx Botão de compartilhar link com feedback "Link copiado"
├── ErrorBoundary.tsx Captura erros de renderização e exibe fallback amigável
├── WhetIntro.tsx Animação transitória de entrada na landing (1x por sessão) — wordmark gigante entra borrado, sofre flash de afiamento com sparks radiais, crispa e morfa pro botão "Whet" do header via `data-whet-target`; respeita prefers-reduced-motion, `?intro=1` força replay, botão skip bilíngue, fade-out no skip, fallback gracioso se morph target não existir
├── ServiceWorkerRegister.tsx Registro do service worker (cache stale-while-revalidate, offline)
└── blog/
└── PostLayout.tsx Layout compartilhado entre posts do blog (header, reading time, prev/next)src/lib/ Camada de backend do produto (só usada por /api/rewrite)
├── providers/
│ ├── types.ts Contrato RewriteProvider (name, limits, isAvailable, submit)
│ ├── gemini.ts Gemini 2.5 Flash (AI Studio free tier)
│ ├── mistral.ts Mistral Small (La Plateforme free)
│ ├── groq.ts Llama 3.3 70B via Groq (free tier diário)
│ └── index.ts availableProviders() + getProviderByName()
├── rotator.ts LRU + janelas deslizantes (rpm/rpd) + cooldown em falha
├── rate-limit.ts Travamento por IP (10 req/hora, janela deslizante)
├── live-ranking.ts Persistência do ranking ao vivo em Redis sorted set (fire-and-forget; nunca armazena texto do prompt)
└── usage-stats.ts Contadores de uso no Redis (rewrites/dia, provider, país, score buckets) via pipelinesrc/app/api/
├── benchmark/route.ts GET — agrega resultados do cross-model benchmark
├── rewrite/route.ts POST — rota principal da reescrita one-click
└── rewrite/next/route.ts GET — consulta leve do rotador (qual provider tocaria agora)Além das rotas /api, o blog expõe src/app/blog/rss.xml/route.ts (GET) servindo um feed RSS 2.0 dos posts.
Layout de resultado — split view
Após análise, a tela se divide em dois painéis lado a lado (empilhados em mobile):
Painel esquerdo — Prompt anotado: o texto do usuário com linhas coloridas por severidade (vermelho/âmbar/azul), números de linha, e highlight inline nas palavras-gatilho que dispararam a detecção.
Painel direito — Prompt reescrito (
RewritePanel): quatro estados —- Idle: botão "Reescrever com IA" + label "próxima IA disponível: Gemini 2.5 Flash" (consulta leve a
/api/rewrite/nextpra saber quem vai tocar) + disclosure de privacidade + link pra/privacy - Loading: skeleton de tijolos montados em cascata (
brickAssemble, com respiração em loop), streak de luz entrando pela esquerda como ponte causal do painel anterior, scanline ambiente em loop pra manter vivo enquanto a API responde - Done: prompt reescrito com atribuição (
✨ Reescrito por X), celebração de score (+Δ animado, count-up, halo radial, sparkles), botões "Copiar" e "Reescrever com outra IA" - Error: fallback automático (all_exhausted / rate_limited / text_too_long / provider_failed) — em qualquer falha, a instrução de correção fica visível embaixo pra uso manual na IA de preferência do usuário
- Idle: botão "Reescrever com IA" + label "próxima IA disponível: Gemini 2.5 Flash" (consulta leve a
Abaixo — Diagnósticos (progressive disclosure): uma linha curta "N padrões detectados" seguida de uma faixa de chips — um por regra detectada, colorido pela severidade, com contagem e chevron. Chips começam colapsados; click num chip expande a seção daquela regra in-place, revelando os cards completos (trecho com highlight, sugestão, dica de reformulação, razão). Click numa linha colorida do painel esquerdo expande automaticamente o grupo certo e rola até o card correspondente. Nada é escondido — só desdobrado sob demanda, pra que o resultado primário (prompt reescrito) não seja abafado por informação secundária.
Transição coreografada entre os dois painéis: quando o usuário clica "Reescrever com IA", cada linha do painel esquerdo dissolve em cascata (stagger capado em 400ms total, blur + skewX + translateX), enquanto um streak luminoso entra no painel direito simbolizando a passagem do prompt de um lado ao outro. Quando a resposta chega, o container do prompt reescrito materializa via blur-to-sharp, e a celebração de score entra 650ms depois. Se o status é done, as linhas do esquerdo voltam em modo dimmed (opacity 0.55, sem cores de severidade) pra permitir comparação visual limpa entre o original e o reescrito. Respeita prefers-reduced-motion.
A instrução de correção não some — fica acessível num acordeão "Ver instrução de correção" na base do painel direito, preservando o fluxo manual e a auditabilidade pra quem quer entender o porquê da reescrita.
Fluxo completo
1. Usuário abre o site (LandingView)
— idioma da UI é detectado do browser, com toggle PT/EN no header para troca manual
2. Cola o system prompt na caixa de texto (PromptInput)
— ou seleciona um quick-pick / exemplo do painel ExamplePrompts
3. Clica "Analisar" (ou Ctrl+Enter)
4. Home chama analyze(text) e salva o hash na URL
5. O analyzer percorre cada regra (splitIntoStatements divide o texto):
├── imperative-overload → warnings + tip + highlight
├── redundant-default → infos + highlight
├── cognitive-overload → warning/error por volume (tolera prompts estruturados: listas numeradas ou prosa com ≥3 seções em negrito)
├── vague-instruction → infos + highlight
├── redundant-repetition → warnings por grupo semântico
├── command-over-question → infos + tip + highlight
├── threat-framing → warnings + tip + highlight
├── role-inflation → infos + tip + highlight
├── conditional-reward → infos + tip + highlight
├── tone-domain-mismatch → warnings + tip + highlight (cruza domínio sensível com tom casual)
└── contradiction → warnings + tip (detecta pares de antônimos no mesmo prompt)
6. O analyzer junta tudo, calcula o score, gera a instrução de correção (renderer)
├── Detecta domínio (saúde, direito, marketing, etc.)
├── Gera exemplos concretos por adequação
└── Inclui instrução de formato (texto corrido, sem markdown)
7. Transição animada para AnalysisView (split view)
8. Painel esquerdo: prompt anotado com linhas coloridas e highlights
9. Painel direito: RewritePanel em estado idle — botão "Reescrever com IA"
+ label "próxima IA disponível: X" via GET /api/rewrite/next
10. Abaixo: faixa de chips (1 por regra, colapsados por padrão); click em chip
expande a seção daquela regra com os cards completos in-place
11. Barra superior: score ring animado + Editar prompt + JSON + Compartilhar
12. Usuário clica "Reescrever com IA"
├── Painel esquerdo dissolve em cascata (lineDissolve, stagger capado)
├── Painel direito entra em loading: streak + skeleton de bricks +
│ scanline ambiente em loop (vivo enquanto espera)
└── POST /api/rewrite chama o backend
13. Backend /api/rewrite:
├── checkRateLimit(IP) — 10 req/hora, janela deslizante
├── size cap (4000 chars)
├── gate de diagnóstico: só reescreve prompts com padrões detectados
├── pickNext() — LRU filtrado por cota disponível entre Gemini,
│ Mistral e Groq (rotator com janelas de rpm + rpd + cooldown)
├── provider.submit(metaPrompt) — chama a IA escolhida
├── retry automático em outro provider se o primeiro falhar
└── analyze(rewritten) — re-análise pra obter scoreAfter
14. Done: container materializa (blur-to-sharp), celebração entra
├── delta grande (+14) com gradiente e pop animation
├── count-up do score antes → depois (900ms, easeOutCubic)
├── halo radial + shimmer horizontal + sparkles
└── atribuição "✨ Reescrito por Gemini 2.5 Flash"
15. Painel esquerdo volta em modo dimmed (sem cores) pra comparação
16. Instrução de correção disponível num acordeão "Ver instrução de correção" se o
usuário quiser entender o porquê ou preferir o fluxo manualA análise é 100% client-side (core roda no browser, texto nunca sai do dispositivo). A reescrita é opcional e client → backend → provider: o texto da instrução de correção viaja até o rotador e daí pra IA escolhida, sem armazenamento em nenhum ponto. Em caso de exaustão dos free tiers, o painel degrada automaticamente pra mostrar a instrução de correção copiável, preservando o fluxo clássico. A URL com hash base64 permite compartilhar análises.
Detalhes de privacidade, rotação, limites e política de cada provider em /privacy (o código da rota em src/app/privacy/page.tsx).
CLI
O mesmo core do site roda como ferramenta de terminal. Compila direto com o TypeScript do projeto — zero deps novas em runtime, só tsc em build time.
npm run build:cli # compila src/core + src/cli para dist/
node bin/whet.js -h # mostra a ajuda
node bin/whet.js prompt.txt
echo "Você é o melhor do mundo..." | node bin/whet.js -
node bin/whet.js prompt.txt --jsonExit code 0 quando o score é ≥ 90, 1 entre 60 e 89, 2 abaixo de 60 ou na presença de erros — feito pra servir de step num pre-commit ou CI check. Saída ANSI colorida em TTY, plain text quando a stdout é pipe/redirect. O entry point src/cli/index.ts importa direto de ../core/analyzer — nenhuma regra é reimplementada, o que dá a garantia de que produto web e CLI nunca divergem.
Stack
- TypeScript — core do linter + web app + backend do produto
- Next.js — interface, API routes, rotator em memória
- Tailwind CSS v4 — estilos utilitários
- Regras estáticas — determinísticas, sem dependência de IA
- Providers externos — Gemini 2.5 Flash, Mistral Small, Llama 3.3 70B via Groq (rotação LRU)
whet/
├── src/
│ ├── app/ Next.js — páginas (/, /privacy, /whet-benchmark) + rotas /api (benchmark, rewrite, rewrite/next)
│ ├── core/ Engine do linter (TS puro — compartilhado entre site, API e CLI)
│ ├── lib/ Backend do produto — providers, rotator LRU, rate-limit (só consumido por /api)
│ ├── cli/ Entry point da versão de terminal (src/cli/index.ts)
│ └── components/ Componentes visuais (inclui RewritePanel)
├── bin/
│ └── whet.js Wrapper CJS que aponta pro bundle compilado em dist/cli/
├── whorl/
│ └── benchmark/ Whet Benchmark — cross-model (corpus, providers, runner, results)
├── tests/
│ └── cli-integration.test.js Testes de integração do CLI
├── public/
│ ├── robots.txt Diretivas para crawlers
│ └── sitemap.xml Mapa do site para SEO
├── package.json Dependências, scripts e bin entry (whet)
├── tsconfig.json Configuração do TypeScript (web)
├── tsconfig.cli.json Configuração do TypeScript (CLI — compila só core + cli)
├── next.config.ts Configuração do Next.js
└── README.md Documentação do produtoReferência rápida para desenvolvimento
Setup inicial em máquina nova
git clone https://github.com/luanmadson/whet.git
cd whet
npm install # resolve node_modules
cat > .env.local <<EOF
PORT=3001
# === Providers de reescrita (produto) ===
GEMINI_API_KEY=... # https://aistudio.google.com/app/apikey (free)
MISTRAL_API_KEY=... # https://console.mistral.ai/api-keys (free tier)
GROQ_API_KEY=... # https://console.groq.com/keys (free tier diário)
# === Persistência (Redis — live ranking + usage stats) ===
REDIS_URL=... # Upstash ou qualquer Redis compatível com ioredis; sem essa var o ranking ao vivo é desativado silenciosamente
# === Providers extras (apenas benchmark, fora da rotação do produto) ===
DEEPSEEK_API_KEY=... # https://platform.deepseek.com (top-up mínimo $2, atende V3 e R1)
AI21_API_KEY=... # https://studio.ai21.com/account/api-key (trial gratuito — só benchmark)
EOF
npm run build:cli # compila CLI
npm run dev # sobe em http://localhost:3001As chaves alimentam dois caminhos independentes:
Reescrita one-click do produto (
/api/rewrite→ rotação LRU entre os providers configurados). Sem chave nenhuma, o botão "Reescrever com IA" cai direto pro estado "todas as IAs gratuitas exauridas" e o usuário só consegue copiar a instrução de correção — ou seja, o produto funciona, mas a feature principal fica dormente. Pelo menos uma chave deveria estar configurada em produção.Benchmark cross-model (
whorl/benchmark/runner.js). Providers sem chave são pulados silenciosamente. ADEEPSEEK_API_KEYé exclusiva do benchmark — não entra na rotação LRU do produto porque DeepSeek deixou de ser free tier (exige top-up mínimo de $2, verwhorl/benchmark/README.md). Com uma chave configurada, o runner ligadeepseek-chat(V3) edeepseek-reasoner(R1) automaticamente.
npm run benchmark:dry lista quais providers estão reconhecidos no momento — útil pra confirmar o setup sem gastar rate limit.
Produto
npm run dev # dev server (porta definida em .env.local, default 3001)
npm run build # build de produção (Next.js)
npm run build:cli # compila core + cli para dist/
npm test # build:cli + testes de integração CLI
node bin/whet.js <arquivo> # analisa um prompt via terminalDev server — operação
Cross-platform (Windows/Mac/Linux). Todos leem PORT do .env.local.
npm run check-dev # verifica se está respondendo (exit 0/1/2)
npm run kill-dev # mata o processo que estiver na porta
npm run fresh-dev # kill + apaga .next + sobe dev (resolve zombies e cache corrompido)O caso clássico de "quebrou do nada e ninguém sabe por quê" geralmente é .next/ corrompido por um next build misturado com next dev. npm run fresh-dev resolve em 1 comando. Sintomas típicos: HTTP 500 com mensagens tipo InvariantError: Expected clientReferenceManifest to be defined ou TypeError: __webpack_modules__[moduleId] is not a function.
Whet Benchmark
O Whet Benchmark mede quanto um LLM consegue afiar um prompt mal-escrito sem destruir a intenção original. É uma habilidade central da categoria de prompt-engineering-by-LLM (DSPy, OPRO, PRewrite, PromptWizard, meta-prompting) que nenhum benchmark público avalia diretamente — MMLU mede conhecimento, HumanEval mede código, needle-in-haystack mede atenção em contexto longo, τ-bench mede comportamento agentic. Meta-prompt-following sob pressão pra preservar intenção é um buraco, e o Whet Benchmark existe pra preenchê-lo.
Visível em /whet-benchmark em dois eixos complementares:
- Corpus (default): rigoroso, rodado via
runner.jssobre 9 prompts frescos por run (3 por idioma, sem repetir entre runs) em todos os providers disponíveis. O ranking é cumulativo (todas as runs, deduplicado por prompt×provider) — mede generalização real, não aderência a um set fixo. O deltascoreAfter − scoreBeforeé o que se mede: quanto mais alto e estável em modelos diferentes, mais forte a evidência de que os padrões catalogados pelo linter são reais e transferíveis. - Ao vivo (
?tab=live): ranking paralelo construído a partir das chamadas reais de/api/rewritefeitas pelos usuários do site. Persiste apenas{providerId, scores, delta, latência, timestamp}— nunca o texto do prompt. Rolling 30 dias, agregação por provider com sample count explícito. Responde "sobrevive ao mundo real?". Não é same-input (o rotator LRU distribui a carga), então só vira comparável em escala.
Um provider pode dominar no Corpus (entradas controladas) e perder no Ao Vivo (prompts reais desestruturados) — esse tipo de divergência é o achado mais interessante que o sistema pode gerar. Tese completa, metodologia, limitações honestas e trajetória em whorl/benchmark/README.md.
npm run benchmark:dry # lista quais providers estão configurados (sem chamar APIs)
npm run benchmark # roda os 9 prompts do corpus atual em todos os providers
node whorl/benchmark/runner.js --providers=gemini,claude-cli
node whorl/benchmark/runner.js --prompts=prompt-a-pt,prompt-b-en
node whorl/benchmark/merge-retry.js # mescla Run retry parcial na anterior (ver doc)Chaves em .env.local (gitignored): GEMINI_API_KEY, MISTRAL_API_KEY, GROQ_API_KEY. Provider sem chave é pulado silenciosamente. Claude via CLI não precisa de chave — aproveita a subscription Claude Code. Metodologia completa em whorl/benchmark/README.md.
Convenções
- Idioma do código: variáveis e tipos em inglês; comentários e documentação em português
- Commit messages:
feat(<escopo>): ...oufix(<escopo>): ... - Regras novas: criar em
src/core/rules/, registrar emrules/index.ts, adicionar metadados emrule-meta.ts - Testes:
tests/cli-integration.test.jscobre o CLI; sem testes unitários para regras individuais (lacuna conhecida)
