agentsclimarketplace

Qa loop

Skill pedroberaldo87/pedro-plugins/plugins/qa-loop/skills/qa-loop

Marketplace privado de 17 plugins (skills, hooks e automações) pessoais para Claude Code.

Install
npx -y skills add pedroberaldo87/pedro-plugins --skill qa-loop

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use quando o usuário quer revisar código num loop disciplinado que para por retornos decrescentes (qualidade vs vale a pena), não por zero erros. Ancora no plano de implementação — toda rodada checa fidelidade ao plano e classifica cada finding em implementação, plan-drift ou plano-falho; conserta só implementação com regression gate por conserto; materializa accepted-limits; e entrega relatório humano (HTML) + journal agêntico pra avaliar o nº ideal de loops com o tempo. A skill tem duas fases — uma assintótica de review (severidade variável; e2e/Playwright vira actionable, não bloqueia) e uma Fase Gate absoluta (lint, type, unit e integração 100% verdes no repo inteiro, pré-existentes incluídos, ou gate-red). O motor roda como um Workflow determinístico all-Opus (Opus revisa, Opus planeja, Opus conserta). Substitui /qa, /rev6 e /iterate. Trigger em "/qa-loop", "/qa", "/rev6", "audita o plano", "tá 100%", "revisa isso", "review multi-ângulo", "loopa o review", "revisa até valer a pena".

SKILL.md

49.6 KB, as published. Nobody here has run it

/qa-loop — Loop de review→conserto disciplinado

Revisa código num loop e para quando não vale mais a pena — não quando chega a zero. Ancora no plano de implementação: o código não pode "melhorar" e afastar do que foi combinado.

Substitui três skills: o esqueleto de loop multi-agente do antigo /qa, as lentes paralelas do antigo /rev6, e a disciplina de regression-gate + baseline do antigo /iterate — tudo num fluxo só.

A ideia central (por que existe)

Um loop "conserte até zero erros" tratado como convergente sobre um problema assintótico (scrubber/parser/ranker/regex/prompt — espaço de input infinito) nunca para sozinho: um finder adversarial SEMPRE acha mais um. "Zero findings" é assíntota, não estado. A skill troca o critério por "uma rodada inteira sem finding novo de severidade real fora dos limites já aceitos" — e gasta a maior parte da disciplina em não gerar regressões ao consertar. (Esse é o eixo assintótico. O outro eixo — os checks objetivos lint/type/unit/integração — é absoluto e vive na Fase Gate; ver "As duas fases".)

A arquitetura em uma frase — motor = Workflow, casca = skill

O motor (revisar → planejar → consertar → checar, em N rodadas) roda como um único Workflow determinístico (a tool Workflow). Cada papel é um agente no tier certo — todos Opus 5, o que muda por etapa é o effort: Opus Revisor (independente) acha, Opus Planejador (adversarial, árbitro único) adjudica, Opus Executor conserta um fix por vez. O gate de regressão, o gate de severidade, o churn detector e a parada são lógica do script (código JS) — não "o Opus lembrar de aplicar a regra a cada rodada".

A casca (esta skill, no loop principal Opus) faz só o que o Workflow não consegue: os 2 toques humanos. O Workflow roda em background e não pergunta nada no meio, então:

  1. Antes de disparar o Workflow — classifica o domínio (pergunta 1× se ambíguo) e lê o journal.
  2. Depois do Workflow retornar — gera o relatório HTML + journal e pede a nota 0-10.

Invocar /qa-loop é o opt-in pra orquestração multi-agente — a skill instrui a chamar Workflow. Por que Workflow e não sub-agente solto: sub-agente solto seria o Opus disparando Task ad-hoc (a regra global do usuário condena, e o guard PreToolUse(Agent) acorda a cada fix). Por que não Agent Team: o fluxo é um pipeline fechado, não conversa aberta entre teammates. Workflow dá determinismo + telemetria + resumível.

As duas fases — máquina assintótica + gate absoluto

A skill roda duas fases com lógicas próprias que convivem — não uma máquina só. Cada finding pertence a UMA delas, e cada fase tem seu próprio critério de "pronto". Confundir as duas é o erro que esta seção existe pra matar: tratar um erro de type como "finding de severidade rebaixável", ou tratar um cheiro de código como "tem que zerar".

Fase Assintótica — revisão de código, aderência ao plano (os 3 buckets), findings de severidade variável (P0-P3), accepted-limits, parada por retornos decrescentes. e2e/Playwright vive aqui: cobertura parcial e honesta, vira actionable no relatório — não bloqueia. É todo o corpo desta skill (os passos abaixo descrevem ela).

Fase Gate — lint, type-check, teste unitário, teste de integração e afins. Absolutista: 100% verde no repo inteiro ou não passou. Binário. Sem severidade, sem julgamento, sem accepted-limit, sem "não vale a pena". É portão. Os checks nunca foram da fase assintótica — são a Fase Gate. Detalhe operacional no Passo 8.0 (gate de saída) + na seção "Detecção de rede".

As fases não são sequenciais no tempo. O gate é invariante de saída, protegido continuamente pelo regression-net por-fix do motor (a suíte roda a cada conserto). A skill só declara sucesso da sessão quando AS DUAS fecham: a Fase Assintótica chega a uma rodada limpa (seu evento interno — retornos decrescentes) E a Fase Gate fica 100% verde. "Rodada limpa" é o desfecho do motor; "sucesso da sessão" é das duas juntas. Gate vermelho nunca é sucesso — a casca seta stopReason='gate-red'.

Input

/qa-loop <alvo> [--plan=<path>] [--floor=P1] [--max-rounds=6] [--domain=auto] [--headless]
  • <alvo> — caminho/pasta, diff ref (HEAD~3..HEAD, staged), PR (#123), ou descrição natural do que foi implementado.
  • --plan — o plano de implementação a auditar contra (.claude/plans/*.md, docs/specs/*.md). Se ausente, tenta achar o plano mais recente; se não houver, roda em modo "review sem plano" (sem o bucket plan-drift/plan-flaw, só implementação — e avisa que está sem âncora).
  • Knobs (todos com default, abaixo).

Modelo & effort por etapa (R8) — contrato em references/r8-tiers.md

É TUDO Opus 5 (contrato R8 desde 2026-07-26): os seis knobs rodam model: 'opus' e o que dispara no tier certo pro PESO da decisão é o effortxhigh no sweep e no confirm-pass, medium na fila mecânica. A semântica dos knobs é o contrato R8 compartilhado com o /sovai, vendorado em references/r8-tiers.md (fonte: _shared/r8-tiers.md — não editar a cópia à mão; scripts/sync-shared.sh --check pega drift). Trocar o tier de uma etapa lá vale pros dois motores. A tabela completa (Etapa · Modelo · Effort · Knob + o que cada knob significa + a regra de tier por rodada) está no arquivo. Abaixo, só onde cada knob entra NESTE motor (revisa→planeja→conserta):

KnobOnde no motor
decompose_modelREVIEW + PLAN da rodada 1 (sweep completo + plano pesado).
coordinate_modelREVIEW + PLAN das rodadas 2+ (caça-regressão + delta).
executor_modelEXEC — aplica 1 fix do bucket 1 por vez.
mechanical_modelFila objetiva da Fase Gate (Passo 8.0) — lint/type/unit/integração, sem julgamento.
diagnose_modelEscalada de churn — mesma função regrediu ≥ churn_threshold → diagnóstico de raiz, não mais remendo.
finalize_modelConfirm-pass — antes de declarar rodada limpa, um re-sweep completo dedicado roda aqui; não confia no resultado mais barato (coordinate_model) da rodada que pareceu limpa.

Parâmetros / knobs

Os 6 model-knobs (decompose_model, coordinate_model, executor_model, mechanical_model, diagnose_model, finalize_model) com seus defaults de modelo/effort vivem no contrato R8 (references/r8-tiers.md) — não repito os defaults aqui pra não reintroduzir o drift. Os knobs de comportamento do loop:

KnobDefaultO que faz
severity_floorP1Conserta P0/P1; P2/P3 viram candidato a accepted-limit. Load-bearing: define "finding de severidade real".
max_rounds6TRAVA DE INCÊNDIO, NÃO META. Quem decide a parada é o gate de severidade. Clampado pela camada de rede no motor: Camada 4 → 2, Camada 5 → 1 (rede fraca não ganha mais voltas de aposta cega).
domainautoauto infere; convergent (tem comando pass/fail objetivo) ou asymptotic.
regression_gateonSempre on — é o coração.
triage_threshold2Com ≥2 findings (ou qualquer alargamento de regra), o PLAN vira tabela formal; com 1, decisão inline.
churn_threshold2≥N regressões auto-infligidas na mesma função → escala pro diagnose_model (para de remendar).
headlessoffModo não-interativo (pro /sovai) — nunca pergunta, alertas de plano não viram fix.

Precedência (R3): flag de invocação > .claude/qa-loop.config.md do projeto > default acima.

Fluxo

digraph qa_loop {
    rankdir=TB;
    "Start" [shape=doublecircle];
    "CASCA: 0. domínio + lê journal" [shape=box];
    "CASCA: 1. contrato + baseline + rede" [shape=box];
    "WORKFLOW: motor (Fase Assintótica)" [shape=box, style=filled];
    "CASCA: 8.0 FASE GATE (lint/type/unit/integração — 100% repo)" [shape=diamond];
    "CASCA: 8. relatório HTML + journal + nota" [shape=doublecircle];

    "Start" -> "CASCA: 0. domínio + lê journal" -> "CASCA: 1. contrato + baseline + rede";
    "CASCA: 1. contrato + baseline + rede" -> "WORKFLOW: motor (Fase Assintótica)";
    "WORKFLOW: motor (Fase Assintótica)" -> "CASCA: 8.0 FASE GATE (lint/type/unit/integração — 100% repo)";
    "CASCA: 8.0 FASE GATE (lint/type/unit/integração — 100% repo)" -> "CASCA: 8.0 FASE GATE (lint/type/unit/integração — 100% repo)" [label="vermelho → conserta (fila objetiva, mechanical_model) → re-roda"];
    "CASCA: 8.0 FASE GATE (lint/type/unit/integração — 100% repo)" -> "CASCA: 8. relatório HTML + journal + nota" [label="travou → gate-red (BLOQUEANTE)"];
    "CASCA: 8.0 FASE GATE (lint/type/unit/integração — 100% repo)" -> "CASCA: 8. relatório HTML + journal + nota" [label="verde / gate vazio declarado"];
}

O loop interno (dentro do Workflow), por rodada:

digraph motor {
    rankdir=TB;
    "REVIEW (decompose/coordinate_model)" [shape=box];
    "Gate severidade (JS)" [shape=diamond];
    "CONFIRM (finalize_model, xhigh)" [shape=box, style=filled];
    "PLAN (decompose/coordinate_model)" [shape=box];
    "EXEC (executor_model)" [shape=box];
    "Gate regressão (JS)" [shape=diamond];
    "Reverte (refaz no tier da rodada)" [shape=box];
    "DIAGNOSE (diagnose_model, xhigh)" [shape=box, style=filled];
    "PARA — rodada limpa" [shape=doublecircle];
    "Próxima rodada" [shape=box];

    "REVIEW (decompose/coordinate_model)" -> "Gate severidade (JS)";
    "Gate severidade (JS)" -> "CONFIRM (finalize_model, xhigh)" [label="parece limpa (rodada completa, 0 severo)"];
    "CONFIRM (finalize_model, xhigh)" -> "PARA — rodada limpa" [label="confirma limpa"];
    "CONFIRM (finalize_model, xhigh)" -> "PLAN (decompose/coordinate_model)" [label="achou algo que a rodada barata perdeu"];
    "Gate severidade (JS)" -> "PLAN (decompose/coordinate_model)" [label="há P0/P1"];
    "PLAN (decompose/coordinate_model)" -> "EXEC (executor_model)";
    "EXEC (executor_model)" -> "Gate regressão (JS)";
    "Gate regressão (JS)" -> "Reverte (refaz no tier da rodada)" [label="suíte vermelha, churn<teto"];
    "Gate regressão (JS)" -> "DIAGNOSE (diagnose_model, xhigh)" [label="churn>=teto"];
    "Reverte (refaz no tier da rodada)" -> "EXEC (executor_model)";
    "Gate regressão (JS)" -> "Próxima rodada" [label="verde / fim do bucket"];
    "Próxima rodada" -> "REVIEW (decompose/coordinate_model)";
}

Tier por rodada: rodada 1 = decompose_model (Opus xhigh, planejamento inicial); rodadas 2+ = coordinate_model (Opus high, coordenação rotineira). O nó CONFIRM é dedicado — sempre finalize_model (Opus xhigh), nunca o tier da rodada que pareceu limpa. DIAGNOSE também é dedicado — sempre diagnose_model (Opus xhigh), disparado só quando o churn escala (mesma função regredindo repetidamente).


CASCA — Passo 0 · Classificar domínio + ler journal

Toda sessão roda AS DUAS fases (ver "As duas fases"). A pergunta de domínio NÃO roteia a skill pra um lado só — ela faz duas coisas: define o conteúdo da Fase Gate E calibra o teto da Fase Assintótica.

Pergunta binária e barata: "existe UM comando objetivo com pass/fail (testes/build/lint/type/um curl com jq -e)?"

  • Convergente — sim, há checks objetivos. Eles compõem a Fase Gate (Passo 8.0): lint/type/unit/ integração rodam ao fim como portão absoluto (100% verde no repo inteiro, ou stopReason='gate-red'). Quanto mais forte a suíte, mais forte o gate; o alvo objetivo também dá teto alto pra Fase Assintótica.
  • Assintótico (default pra heurística — scrubber/parser/ranker/regex/prompt/classificador/"achar todos os bugs") — não há estado-alvo binário pro review; governa o gate de severidade e a parada por retornos decrescentes da Fase Assintótica. É o caso primário desta skill. O domínio NÃO decide se há Fase Gate: se o projeto tem checks objetivos (até um scrubber tem lint+unit), eles compõem a Fase Gate absoluta igual ao caso convergente — o domínio só muda o teto/severidade da fase assintótica. Só SEM nenhum check objetivo a Fase Gate fica vazia, declarada honesto — nunca finge gate.

Se ambíguo e não headless, pergunta 1× ao usuário. Se headless, assume asymptotic (mais conservador).

Lê o journal (R7) antes de disparar o motor: do .claude/qa-loop.config.md do projeto puxa os accepted_limits ratificados + as invariants vivas; de ~/.claude/qa-loop/journal/learnings.md puxa os aprendizados cross-projeto que afinam os prompts do Revisor/Planejador. Tudo isso vira args do Workflow.

⚠️ Erro que mata o loop: tratar assintótico como convergente → loop infinito. A pergunta tem que ser real, não um carimbo.

CASCA — Passo 1 · Declarar contrato + baseline + rede de regressão

Fixa e anuncia no header: teto de rodadas (safety-cap), severity_floor, o plano-âncora, e a camada de rede de regressão disponível (ver Guard-rails → Detecção de rede). Snapshot do nº de erros de lint/typecheck ANTES da rodada 1 (baseline_errors) — é o que a rede de regressão DURANTE o loop usa pra pegar regressão estrutural (não piorar o baseline mid-loop). ⚠️ Isso é a rede in-loop, NÃO o gate: a Fase Gate de saída (Passo 8.0) exige lint/type/unit/integração absolutamente 0 no fim, pré-existentes incluídos — o baseline tolera no meio, o gate não tolera no fim. Declara honestamente o blast-radius que a rede NÃO cobre. Esses valores entram nos args do Workflow.


WORKFLOW — o motor (loop de rodadas)

A casca dispara a tool Workflow com o script abaixo. Esqueleto de referência — o princípio, não código imutável. Os três schemas (FINDINGS, PLAN, EXEC_RESULT) são o que torna os gates determinísticos: o script lê campos estruturados, não texto solto.

export const meta = {
  name: 'qa-loop-engine',
  description: 'Motor de QA: tier por etapa (R8) — decompose/coordinate/executor/mechanical/diagnose/finalize',
  phases: [{ title: 'Review' }, { title: 'Plan' }, { title: 'Exec' }, { title: 'Confirm' }],
}

// args (vindos da casca): { target, planPath, severityFloor, maxRounds, domain,
//   safetyLayer, churnThreshold, acceptedLimits[], invariants[], learnings }
const sevRank = s => ({P0:3, P1:2, P2:1, P3:0}[s] ?? 0)
const floor = sevRank(args.severityFloor || 'P1')
let acceptedLimits = args.acceptedLimits || []
let invariants = args.invariants || []
const churn = {}                 // { 'arquivo:função': nº de regressões }
// chave do churn = arquivo:função. Só o nome da função colidiria entre arquivos
// (dois `validate()` em módulos diferentes) e escalaria o diagnóstico caro cedo demais.
const churnKey = fix => `${fix.file || '?'}:${fix.fn || '?'}`
const rounds = []
let cleanRound = false, churnEscalated = false, r = 0
let touchedLastRound = [], openFindings = []   // delta pro REVIEW das rodadas 2+

// Teto de rodadas: default DENTRO do motor (sem ele, args.maxRounds undefined faz
// `r < undefined` ser false na 1ª volta — o motor devolveria "limpo" sem ter revisado
// nada), e CLAMP pela camada de rede — "rede fraca baixa o teto" é lógica do script,
// não regra pro modelo lembrar (Camada 4 = teto 2, Camada 5 = teto 1).
const LAYER_CAP = { 4: 2, 5: 1 }
const churnThreshold = args.churnThreshold || 2
const maxRounds = Math.min(args.maxRounds || 6, LAYER_CAP[args.safetyLayer] ?? Infinity)

// Tier por rodada (R8 — tabela única com /sovai): rodada 1 = decompose_model (xhigh,
// planejamento inicial); rodadas 2+ = coordinate_model (high, coordenação rotineira).
const tierFor = round => round === 1
  ? { model: 'opus', effort: 'xhigh' }   // decompose_model
  : { model: 'opus', effort: 'high' }    // coordinate_model

while (!cleanRound && r < maxRounds && !churnEscalated) {
  r++; phase(`Rodada ${r}`)
  const tier = tierFor(r)

  // REVIEW — 1 Opus Revisor INDEPENDENTE, no tier da rodada. Checklist de 6 dimensões
  // (1 agente, não 6). Rodada 1 = sweep completo do material inteiro (decompose_model);
  // 2+ = DELTA (coordinate_model): só os arquivos tocados pelos fixes da rodada anterior
  // + findings abertos — caça-regressão, não releitura do material inteiro.
  const review = await agent(reviewPrompt({ round: r, acceptedLimits, invariants,
      scope: r === 1 ? 'full' : { touchedFiles: touchedLastRound, openFindings } }),
    { model: tier.model, effort: tier.effort, phase: 'Review', schema: FINDINGS, agentType: 'voltagent-qa-sec:code-reviewer' })

  // GATE de severidade (JS) — sobre rodada COMPLETA (todo ângulo retornou).
  const severe = review.findings.filter(f =>
    sevRank(f.severity) >= floor && !isAccepted(f, acceptedLimits))

  if (review.complete && severe.length === 0) {
    // CONFIRM — finalize_model (Opus xhigh, R8 "revisão final e integração"). Re-sweep
    // completo DEDICADO antes de declarar limpa — não confia no resultado mais barato
    // (coordinate_model) da rodada que pareceu limpa.
    const confirm = await agent(reviewPrompt({ round: r, acceptedLimits, invariants, confirming: true }),
      { model: 'opus', effort: 'xhigh', phase: 'Confirm', schema: FINDINGS, agentType: 'voltagent-qa-sec:security-auditor' })   // finalize_model
    const confirmSevere = confirm.findings.filter(f =>
      sevRank(f.severity) >= floor && !isAccepted(f, acceptedLimits))
    if (confirm.complete && confirmSevere.length === 0) {
      rounds.push({ r, review, confirm, corrections: [], regressions: 0, alerts: [] })
      cleanRound = true; break
    }
    // o confirm-pass achou algo que a rodada barata perdeu — processa, não ignora.
    review.findings = review.findings.concat(confirm.findings)
  }

  // PLAN — Opus Planejador ADVERSARIAL = árbitro único (R2), no MESMO tier da rodada.
  // Adjudica "procede?" contra a rubrica, roteia nos 3 buckets, triagem por severidade
  // E risco-de-conflito, sequencia. Rodada 1 (decompose_model) pesada; 2+ (coordinate_model) = só o DELTA.
  const plan = await agent(planPrompt({ review, round: r, invariants, acceptedLimits }),
    { model: tier.model, effort: tier.effort, phase: 'Plan', schema: PLAN, agentType: 'voltagent-qa-sec:error-detective' })

  // EXEC — executor_model (Opus high, R8 all-Opus), SÓ bucket 1, EM SÉRIE (o gate roda a
  // suíte entre fixes; pares de risco exigem ordem).
  const corrections = []; let regressions = 0
  for (const fix of plan.bucket1) {
    const res = await agent(execPrompt({ fix, invariants, safetyLayer: args.safetyLayer }),
      { model: 'opus', effort: 'high', phase: 'Exec', schema: EXEC_RESULT, agentType: 'voltagent-core-dev:backend-developer' })   // executor_model
    // GATE de regressão (JS) — quem decide keep/revert é o SCRIPT, nunca o executor.
    if (res.suiteRegressed) {
      const k = churnKey(fix)
      regressions++; churn[k] = (churn[k] || 0) + 1
      if (churn[k] >= churnThreshold) {
        // DIAGNOSE — diagnose_model (Opus xhigh, R8 "diagnóstico após falhas repetidas").
        // Não é mais "refaz cirúrgico" — é a causa raiz do acoplamento que faz a mesma
        // função regredir de novo a cada tentativa.
        await agent(diagnosePrompt({ fix, churnCount: churn[k], invariants }),
          { model: 'opus', effort: 'xhigh', phase: 'Diagnose', agentType: 'voltagent-qa-sec:architect-reviewer' })   // diagnose_model
        churnEscalated = true; break
      }
      await revertAndMaybeRedo(fix, res, tier)   // reverte; refaz cirúrgico no tier DA RODADA
    } else {
      corrections.push(res)
      if (res.newInvariant) invariants.push(res.newInvariant)   // invariante viva pras próximas rodadas
    }
  }

  acceptedLimits = acceptedLimits.concat(plan.proposedLimits || [])   // propostos (não ratificados — R6)
  touchedLastRound = corrections.flatMap(c => c.files_touched || [])  // delta do REVIEW da próxima rodada
  openFindings = review.findings.filter(f => !corrections.some(c => c.fix_id === f.id))
  rounds.push({ r, review, plan, corrections, regressions, alerts: plan.alerts || [] })
}

return {
  rounds, acceptedLimits, invariants, churn,
  planFlawAlerts: rounds.flatMap(x => x.alerts),
  telemetry: rounds.map(x => ({ round: x.r, corrections: x.corrections.length, regressions: x.regressions,
    findings_by_sev: tallyBySev(x.review.findings) })),
  stopReason: cleanRound ? 'no-severe-finding' : churnEscalated ? 'churn-escalated' : 'max-rounds',
}

Schemas (JSON Schema, resumidos):

  • FINDINGS{ complete: boolean, findings: [{ id, file, line, severity: 'P0'|'P1'|'P2'|'P3', dimension, problem, fix_direction }] }. complete=false se algum ângulo não retornou → NUNCA conta como rodada limpa.
  • PLAN{ bucket1: [{ id, fn, severity, conflict_risk, order, fix_direction }], drift: [...], alerts: [...], proposedLimits: [...], invariants: [...] }.
  • EXEC_RESULT{ fix_id, fn, files_touched: [...], test_name, suiteRegressed: boolean, newInvariant?, note }.

Os agentType: voltagent-* nos spawns são otimização de persona — se o agent type não existir na máquina, spawne sem agentType (o motor não depende deles).

Os passos do motor, em detalhe

REVIEW = 1 Opus Revisor (R1), no tier da rodada (R8). Um único Opus cobre as 6 dimensões como CHECKLIST (arquitetura · backend · frontend · contratos fullstack · correção · UX) — não 6 agentes. Rodada 1 roda em decompose_model (xhigh — sweep completo): recebe o material inteiro + o plano-âncora + os accepted-limits vivos (não re-reportar) + as invariantes vivas (não violar). Rodadas 2+ rodam em coordinate_model (high) e recebem o DELTA, não o material inteiro: os arquivos tocados pelos fixes da rodada anterior + os findings ainda abertos + accepted-limits/invariantes (pequenos) — caça-regressão nas mudanças + ângulos frescos sobre elas. Formato de cada finding: P{0-3} — {arquivo:linha} — {problema} — {direção de fix, SEM código}. Inclui sempre duas âncoras (nas rodadas 2+, as duas restritas ao delta):

  • Plano — "compare contra o PLANO: sinalize onde a implementação DIVERGE do planejado, mesmo que o código pareça bom".
  • Constituição — "leia .claude/docs/quality-goals.md do projeto (as metas de qualidade autorais) e sinalize onde a implementação VIOLA o que está escrito lá". O arquivo é lido na rodada, nunca copiado aqui — a régua é a do projeto que instalou, não a desta skill. Sem esse arquivo, o eixo simplesmente não roda e o REVIEW segue só com o plano — ausência de constituição não é finding.

Regra dura: se algum ângulo do checklist não foi coberto → complete=false, jamais "achou zero".

CONFIRM = Opus dedicado em finalize_model (xhigh, R8 "revisão final e integração"). Quando uma rodada parece limpa (complete && severe.length===0), o motor NÃO declara vitória direto — dispara um re-sweep completo independente em finalize_model, sempre xhigh, mesmo que a rodada que pareceu limpa tenha rodado em coordinate_model. Só confirma limpa se o confirm-pass TAMBÉM achar zero; senão os findings do confirm-pass entram no PLAN da mesma rodada — o resultado mais barato nunca tem a palavra final.

PLAN = Opus Planejador adversarial, árbitro único (R2), no MESMO tier da rodada. Entre "achou" e "consertar". 4 sub-passos: (a) consolida + dedup os findings; (b) rotula cada um por BUCKET (impl / plan-drift / plan-flaw); (c) triagem do bucket 1 por severidade e risco de conflito (3 sinais checáveis ANTES de editar — dois fixes na mesma função / fix que ALARGA regra genérica / fix que viola invariante viva); (d) sequencia (extensão-enumerada primeiro, alargamento por último com negative-tests, agrupa por função). Mandato adversarial: default a REJEITAR um finding (ou um accepted-limit) a não ser que se justifique contra a rubrica. Gerar ≠ julgar — o Revisor acha, o Planejador é quem carimba a severidade (mata a oscilação entre rodadas). Rodada 1 (decompose_model) = plano pesado; 2+ (coordinate_model) processam só o DELTA.

EXEC = Opus Executor em executor_model (R1). SÓ bucket 1, um fix por vez, na ordem do PLAN: (1) test-first (red) — escreve o teste que reproduz o finding e vê falhar; o teste recebe nome com a invariante (test_atravessa_placeholder_pro_par_aws); (2) fix cirúrgico — menor mudança, extensão enumerada > alargamento de regra; (3) roda a SUÍTE INTEIRA; (4) reporta. O executor nunca toca bucket 2/3. Ele ser Opus não relaxa nenhuma dessas travas: o papel é estreito por design, não por limitação de modelo.

GATE de regressão (código, não LLM). O EXEC_RESULT.suiteRegressed é objetivo (a suíte passou ou não). Vermelho → reverte + churn++; o Planejador (no tier da rodada) decide refazer cirúrgico (troca difuso por extensão enumerada / exceção mais específica) — até churn_threshold. A partir do threshold, escala pra diagnose_model (Opus xhigh, R8 "diagnóstico após falhas repetidas"): não é mais "refaz cirúrgico", é achar a causa raiz do acoplamento. Quem decide keep/revert é sempre o script + o Planejador, nunca o executor que fez o fix — é separação de papel, não desconfiança de modelo (agora os dois são Opus).

Bucket 2 (plan-drift) e bucket 3 (plan-flaw) nunca viram edição do executor (R4 e a constraint central abaixo).


CONSTRAINT CENTRAL — QA ancorado no plano (3 buckets)

A skill não é só review de código — audita fidelidade ao plano e à constituição do projeto a cada rodada. Todo finding é roteado em 3 buckets no PLAN:

  1. Implementação — código diverge do plano ou viola a constituição do projeto (.claude/docs/quality-goals.md, quando o projeto tem uma), bug, ou regressão. → fila de conserto (EXEC, com gate). Violar a constituição é divergência igual às outras: entra pela rubrica de severidade normal, sem faixa própria.
  2. Plan-drift (R4) — um "fix" otimizaria o código mas afastaria o comportamento do que foi pedido/planejado. → restaura pro plano automaticamente E o desvio sobe no bucket de alertas como candidato a mudança-de-plano pro usuário julgar. O plano vence a "melhoria", mesmo que o agente ache que faz sentido mudar. Drift é uma classe de regressão — não pausa.
  3. Plano/arquitetura falho — o plano em si é falho (decisão de arquitetura que gera problema crítico). → bucket de ALERTA. NUNCA consertado/implementado no loop. Sobe pro usuário no relatório: "apresento e julgamos". É insumo pro planejamento, não trabalho de QA.

A skill enforça o plano; não o redesenha. Só o bucket 1 vira edição. "QA é QA."

Rubrica de severidade (R2) — 3 faixas, escrita, aplicada pelo árbitro único

  • P0/P1 (acima do floor) — exige gatilho objetivo OU ancorado no plano: quebra comportamento do plano · classe de segurança (secret, injection, authz) · bug com repro determinístico. (Falha objetiva de lint/type/unit/integração NÃO entra nesta escada — é da Fase Gate, absoluta e separada.)
  • P2/P3 (abaixo do floor) — opinião de qualidade sem divergência do plano e sem falha objetiva (naming, gosto, micro-refator). Picuinha nunca sobe sozinha — só vira P1 com bug concreto anexado.
  • O Opus Planejador é o ÁRBITRO ÚNICO que aplica a rubrica. Não são N LLMs cada um carimbando — é um juiz, rubrica escrita, severidade comparável entre rodadas (e na telemetria).

Config em 3 camadas (R3) — .claude/qa-loop.config.md (VERSIONADO)

Fora da pasta ignorada .claude/qa-loop/. Versionado de propósito — viaja com o projeto. Contém:

  • Knobs do projeto (floor, max_rounds, churn_threshold) que sobrescrevem o default.
  • Rubrica do projeto — ajustes de severidade específicos do alvo.
  • Accepted-limits permanentes (ratificados pelo usuário — ver R6) + invariantes vivas.
  • Flags de invocação padrão.

Precedência: flag > config do projeto > default da skill.

Critério de parada (DUAS condições: Fase Assintótica + Fase Gate)

A skill só declara sucesso quando AS DUAS fecham. São critérios independentes.

Fase Assintótica — para se qualquer um (gate de severidade primário, teto = trava):

  • [PRIMÁRIO] uma rodada INTEIRA completou (complete=true) E produziu ZERO findings novos de severidade ≥ floor FORA dos accepted-limits E o confirm-pass dedicado (finalize_model, xhigh) concordou — rodada limpa nunca é declarada só pelo resultado mais barato (coordinate_model) que pareceu limpo.
  • [TRAVA] atingiu max_rounds → reporta "teto atingido sem convergir" (ALARME, não sucesso).
  • [ESCALADA] churn escalou (≥churn_threshold regressões na mesma função) → passa por diagnose_model (xhigh) antes de virar churn-escalated.

Fase Gate — condição ABSOLUTA de sucesso (não é "parada por retorno decrescente"):

  • Sucesso EXIGE a Fase Gate 100% verde (lint/type/unit/integração no repo inteiro, incluindo pré-existentes). Vermelho → vira fila de conserto; se travar, stopReason='gate-red' (fracasso explícito, nunca sucesso). e2e/Playwright NÃO entra aqui — é Fase Assintótica, vira actionable.
  • Projeto sem checks objetivos → Fase Gate vazia, declarada honesto (não é sucesso fingido nem gate-red).

O teto NÃO é meta. Quem decide é o gate de severidade — para na rodada 2 se convergiu rápido, vai até o teto se ainda acha P0. Cravar um número fixo agora seria o erro simétrico ao "até zero". A telemetria (passo 8) é que vai dizer, com o tempo, se vale cravar.

"Vale a pena" (R5) — retornos decrescentes + risco de regressão, NUNCA tokens

O eixo "vale a pena" é o gate de severidade (retornos decrescentes) + o regression gate / churn detector (risco de regressão — o churn é o "agora gera mais regressão que conserto, para"). Tokens = número PASSIVO no journal, nunca eixo de parada nem framing de "custo".


CASCA — Passo 8 · Fase Gate (saída) + Relatório (humano) + Journal (agêntico) — R7

Quando o Workflow retorna, a casca executa a Fase Gate e SÓ ENTÃO produz os artefatos.

Passo 8.0 · FASE GATE — o portão absoluto de saída

Antes de qualquer relatório, a casca roda os checks objetivos do projeto como portão binário:

  • Cache verde (consulta → grava): source "${CLAUDE_PLUGIN_ROOT}/lib/green-cache.sh". Antes de rodar a fila, green_cache_check <repo-root> full: HIT → declara o gate verde via cache e reporta no relatório ("gate 100% via cache — tree <hash>, gravado por <writer> às <ts>") sem re-executar. MISS → roda a fila normal; ao fechar 100% verde, green_cache_mark <repo-root> full qa-loop-gate. Gate vermelho nunca grava. Falha do helper (sem git, fora de repo) → MISS silencioso, roda tudo. Cache HIT não é burla: é a mesma fila, verde, no mesmo tree-hash — qualquer edição invalida.
  • O que roda: lint + type-check + teste unitário + teste de integração. A detecção reusa as camadas já definidas (ver "Detecção de rede"): unit/integração = Camada 1; lint/type = Camada 3. e2e marcado (@pytest.mark.e2e, specs Playwright, script e2e) é EXCLUÍDO daqui → vira actionable na Fase Assintótica (lento demais e dependente de ambiente pra ser portão).
  • Escopo: repo inteiro, absoluto. Exige TODO o lint/type/unit/integração do repo 100% verde — incluindo erros pré-existentes não-relacionados à revisão. "Tá errado = corrige = fim." Sem severidade, sem accepted-limit, sem "retornos decrescentes". (Mesma disciplina do /ship, rodada mais cedo.) Se o projeto oferece um runner com escopo próprio (scripts/run_app_tests.sh, target de Makefile, pnpm --filter, cargo test -p), use-o pra rodar no ambiente certo — nunca pra encolher o que precisa passar; em monorepo de múltiplos ambientes é o que evita falso-vermelho de import.
  • Vermelho → conserta em mechanical_model (fila objetiva, FORA do roteamento de buckets). Erro de lint/type/unit/integração é objetivo e determinístico — não precisa do julgamento de severidade do REVIEW→PLAN, nem do effort caro do executor_model. Vai direto pra uma fila de conserto em mechanical_model (opus/medium, R8 "operações mecânicas e bem delimitadas" — mesmo regression gate por conserto), re-roda o gate, até verde. Não passa pelos 3 buckets (esses são pro review subjetivo da fase assintótica). Se travar de verdade (erro que exige decisão de arquitetura), NÃO declara sucesso — e o conserto que travou escala pra diagnose_model (xhigh) antes de virar gate-red.
  • Quem seta o stopReason FINAL é a CASCA, não o motor. O motor (Workflow) reporta o stopReason da fase assintótica (no-severe-finding / churn-escalated / max-rounds); a casca, pós-gate, computa o stopReason da sessão — promove a gate-red quando o gate trava vermelho, e mantém o do motor quando o gate fecha verde. O relatório lidera com "GATE VERMELHO — bloqueante" sempre que gate-red.
  • Transparência (anti-violação-de-surgical-changes). O conserto repo-wide é real, mas o relatório separa duas pilhas — "consertos da revisão" vs "débito pré-existente que precisei zerar pro gate ficar verde" — cada uma com seu diff, pra o usuário ver e reverter o que quiser.
  • Sem checks no projeto → Fase Gate vazia, declarada honesto ("sem checks objetivos — só a fase assintótica rodou"). Nunca finge portão.

Passada a Fase Gate, a casca produz DOIS artefatos com públicos distintos:

(A) Relatório HUMANO — gerador de actionables, via a skill /visual como parceira

INVOQUE a skill /visual pra renderizar (não reimplemente template nem daemon): o /visual já traz a hierarquia, o "pedido se explica sozinho", o vocabulário banido, o daemon de live-sync e a semântica de copy. Você passa o conteúdo estruturado (do return do Workflow); o /visual resolve template + daemon + path + abre.

A página é EMITIDA por programa, não digitada (desde o visual v1.8.0). Você escreve um spec JSON e roda python3 <visual>/lib/visual_page.py build --spec <f>; o contrato dos blocos sai de visual_page.py schema. Isso não é detalhe de forma — é o que garante, sem depender de você lembrar, as duas regras duras desta seção: nenhum feedback-item nasce selecionado e o contador começa em 0 de N. A estrutura abaixo mapeia assim: faixa de status → bloco callout; gráfico → bloco chart; cada actionable → bloco item (com tri quando for bloqueio) e item_labels: ["✓ Vira ação", "✏️ Ação c/ ajuste", "✗ Descartar"]; fechamento → automático. O build recusa relatório que peça veredito sem nenhuma prova na página.

Estrutura do relatório (no topo → fundo):

  • Faixa de STATUS DA FASE GATE (topo de tudo, read-only): verde "gate 100% — N checks" ou vermelho "GATE VERMELHO — bloqueante" (stopReason='gate-red'). Se houve conserto repo-wide, separa duas pilhas com diff — "consertos da revisão" vs "débito pré-existente zerado" — pra o usuário revisar/reverter. Fica ACIMA de tudo: um gate vermelho é a primeira coisa que ele vê.

  • Gráfico único — findings por severidade: NÃO desenhe o SVG. Emita o bloco chart do spec, com uma entrada por rodada: {"kind":"chart","rounds":[{"label":"R1","p0":2,"p1":6,"p2":4,"p3":1}, …]}. Os números saem direto do return do Workflow; o programa desenha as barras empilhadas P0/P1/P2/P3 e sobrepõe a linha de severidade real (P0+P1) — a composição E a queda no mesmo lugar. Um gráfico só, porque com 4-5 rodadas dois lado a lado apertam. Por que é do programa: coordenada de barra é aritmética sobre número que você já recebeu pronto, e barra fora de escala não dá erro — dá a conclusão errada sobre retornos decrescentes, que é a única leitura pra qual este gráfico existe. Contagem em string é recusada pelo build (nada de coerção silenciosa). + tabela por rodada com as considerações DENTRO da linha (colapsável, o detail do item), NÃO um listão depois. Read-only.

  • 4 categorias de ACTIONABLE — o que sobe pro usuário deixa de ser "alerta genérico" e vira 4 seções, cada achado um feedback-item SELECIONÁVEL (os valores internos keep/change/remove ficam, mas os labels viram "✓ Vira ação" / "✏️ Ação c/ ajuste" / "✗ Descartar" — o /visual autoriza relabelar). Cada item é auto-explicável: título humano (1 linha, sem jargão de código) + porquê/impacto (1-2 linhas) + onde (path); o detalhe técnico fica no colapsável.

    • Estado inicial NEUTRO (regra dura): todo feedback-item nasce SEM seleção — sem radio checked, sem classe state-*. Nenhuma categoria entra pré-marcada como "✓ Vira ação", e isso vale especialmente pras Sugestões: o loop ter PROPOSTO um refator/drift NÃO é "sim" do usuário — é candidato a decisão dele. Razão dupla: (1) força a decisão ativa (não assume "sim" sem querer); (2) mantém o contador honesto — ele conta input:checked, então pré-marcar só a APARÊNCIA (classe state-keep sem checked) dessincroniza: o item parece selecionado e o contador segue em 0. O progresso começa em 0 de N e só sobe no clique.

    O mapeamento (a partir do return):

    1. Importantes — recomendaçãoplanFlawAlerts P0/P1 (decisões de arquitetura do plano: "apresento e julgamos").
    2. Sugestões de melhoriaplan-drift (candidatos a mudança de plano) + refators propostos pelo loop.
    3. Limitações atuais (não quebram, mas importam) ← acceptedLimits propostos + churn hotspots.
    4. Extras (opcionais) ← P2/P3 documentados (picuinhas abaixo do floor, nice-to-have).
  • Fechamento (feedback-box): progresso + observação geral + botões "Aprovar tudo" / "Copiar feedback".

Seleção live → próximo plano (o ciclo fecha aqui). O usuário marca os itens, diz "ok", e a casca lê ~/.claude/visual-state/latest.json (o daemon do /visual) e monta o próximo plano só com os itens marcados "✓ Vira ação" / "✏️ Ação c/ ajuste" (a nota do "ajuste" entra como refinamento). Copy/paste é o fallback quando o daemon está off. É assim que o relatório de QA vira o INPUT do próximo plano, sem retrabalho.

Polish: densidade controlada (sem parágrafo longo), hierarquia por cor + sev-tag, cada categoria com peso visual distinto. Linguagem humana. (Ver EXAMPLE-REPORT.html.)

Se não headless, pergunta o veredito 0-10 do usuário (1 pergunta opcional/skipável) — a âncora subjetiva.

(B) Journal AGÊNTICO — markdown verborrágico, pra "futuro-eu" (3 camadas)

NÃO é pro usuário ler — é o agente passando informação pra ele mesmo do futuro melhorar a skill. (Ver EXAMPLE-JOURNAL.md.)

  1. Telemetria quantitativa — JSONL append-only, 1 linha/sessão, pra calibrar nº de loops/rubrica. Os números saem do return do Workflow — não é contabilidade extra.
    • Por projeto: <projeto>/.claude/qa-loop/telemetry.jsonl (no .gitignore).
    • Agregado cross-projeto: ~/.claude/qa-loop/journal/telemetry.jsonl (sobrevive a reinstalar o plugin).
  2. Aprendizado da SKILL~/.claude/qa-loop/journal/learnings.md (cross-projeto, acumula): findings exaustivos sobre onde o próprio PROCESSO de QA acertou/errou + AÇÃO concreta pra futuro-eu. Ex.: "o usuário rebaixou P1→P2 num finding de naming → AÇÃO: rubrica — naming enganoso só é P1 com bug concreto"; "regressão escapou do gate porque o teste-red não cobria a interação entre estágios → o checklist do Revisor precisa de uma linha sobre interação". Registra também o que FUNCIONOU (pra não regredir o prompt).
  3. Memória de QA do PROJETO — versionada, junto da config (.claude/qa-loop.config.md): invariantes vivas, accepted-limits ratificados, churn hotspots (funções acopladas), plan-flaws recorrentes.

O loop fecha: a skill LÊ o journal no INÍCIO de cada sessão (passo 0)

A memória do projeto semeia os accepted-limits + invariantes; os aprendizados da skill afinam a rubrica/prompt. É assim que "melhora com o uso" — agêntico + humano-no-loop (os aprendizados viram refino da skill pelo agente e pelo usuário, periodicamente). Zero auto-tuning automático no v1. (Por que não autoresearch: o método do Karpathy depende de UMA métrica escalar barata e comparável entre runs, que review NÃO tem; o keep/discard dele já existe aqui no lugar certo — é o regression gate, que decide por teste objetivo.)

Schema da telemetria (1 objeto por linha):

{"ts":"<ISO>","target":"<path/desc>","domain":"asymptotic","severity_floor":"P1","max_rounds_config":6,"rounds_run":3,"corrections_per_round":[5,1,0],"last_round_with_severe_finding":2,"regressions_self_inflicted":1,"accepted_limits":1,"plan_flaw_alerts":1,"tokens_per_round":[...],"stop_reason":"no-severe-finding","user_score_0_10":null}

last_round_with_severe_finding (mediana entre sessões assintóticas) é a resposta empírica ao "3 loops?".


Guard-rails operacionais

Regression gate por conserto (o maior ROI)

A suíte inteira a cada conserto converte 4 dos 5 mecanismos de regressão de "descoberto 3 rodadas depois" em "falha na mesma edição". É o GATE de regressão do motor. Não pule, não bata só o teste novo.

Os 5 mecanismos de regressão (operacionalizados)

  • A — alargamento de escopo: consertar um falso-negativo alargando uma regra genérica cria falso-positivo em outro canto. → extensão enumerada > alargamento; negative-tests logo após. Prevenido no PLAN.
  • B — ordem de curto-circuito: exceção (early-return) antes da regra principal engole o caso de interseção. → ordene checagens positivas fortes ANTES das exceções; teste a interseção. Pego pelo gate.
  • C — proxy sintático grosseiro: uma propriedade superficial (tem "/", é numérico) usada como semântica vale pros dois lados da fronteira. → combine sinais; na dúvida, inverta o default pro lado seguro. Pego pelo gate.
  • D — conflito entre correções: a correção K+n viola a invariante da correção K. → teste nomeado com a invariante; suíte inteira a cada conserto. Prevenido no PLAN (invariantes vivas) + pego pelo gate.
  • E — interação entre estágios: um estágio deixa resíduo que outro consome. → cada estágio neutraliza o que processou; teste a INTERAÇÃO entre estágios. Pego pelo gate.

Detecção de rede — DOIS usos da mesma detecção

As camadas abaixo são detectadas UMA vez e servem a dois propósitos distintos:

  1. Regression net DURANTE o loop (Fase Assintótica) — a suíte roda a cada conserto pra pegar regressão auto-infligida na hora. Aqui "cumprir 100%" = honestidade 100% sobre a rede, nunca fingir segurança; com rede fraca, baixa o teto e declara o blast-radius não-coberto.
  2. Fase Gate de SAÍDA (Passo 8.0) — portão absoluto: só as Camadas 1 e 3 (unit/integração + lint/type) compõem o gate e têm que estar 100% verdes no repo pra declarar sucesso. A Camada 2 (caracterização, escopo parcial) e a Camada 4 (e2e, dependente de ambiente) ficam de FORA do gate — são da fase assintótica. Aqui NÃO há "teto" nem "retorno decrescente" — é binário. ("Teto" só governa o nº de rodadas da fase assintótica quando a rede é fraca; não toca o gate.)

Detecte na ordem:

  • Camada 1 — Suíte de testes (unit + integração). Detecta: test script em package.json · jest/vitest/mocha em devDependencies · pytest em pyproject.toml/setup.cfg · Cargo.tomlcargo test · go.modgo test ./... · target test em Makefile. → compõe a Fase Gate (absoluta). e2e marcado (@pytest.mark.e2e) é separado daqui → Camada 4.
  • Camada 2 — Testes-de-caracterização gerados dos próprios teste-red do gate. Cobre SÓ os findings já tocados — declara isso. Net da Fase Assintótica (não é portão absoluto). Começa vazia (rodada 1 roda com menos rede).
  • Camada 3 — Lint + typecheck. Detecta: eslint.config.*/.eslintrc.*/biome.json→lint; tsconfig.json/pyproject.toml(ruff/mypy/pyright)→typecheck. → compõe a Fase Gate (absoluta). Era "net teto 2" — virou portão: um erro de type não é net fraco, é falha binária.
  • Camada 4 — Jornada Playwright / e2e (alvo com UX). Print + análise da tela (não só DOM). Cobre só o caminho roteirizado. Fase Assintótica — vira actionable, não bloqueia (lento e dependente de ambiente). Teto 2.
  • Camada 5 — Sem rede objetiva. Fase Gate vazia, declarada honesto. A Fase Assintótica NÃO roda como se houvesse proteção ("sem testes, sem lint/types, sem jornada — cada conserto é aposta cega; rodo no máx 1 rodada de críticos e paro"). Teto 1.

A camada escolhida (e o que ela não cobre) é arg do Workflow (safetyLayer), entra no header da rodada e no relatório. Os tetos das Camadas 4 e 5 não são conselho ao modelo — o motor clampa maxRounds por LAYER_CAP, então rede fraca não consegue gastar 6 rodadas de aposta cega nem que o prompt esqueça a regra.

Churn detector

Por FUNÇÃO (não arquivo): conta edições + regressões auto-infligidas. ≥churn_threshold regressões na mesma função → escala (para de remendar; sinaliza "acoplamento alto: refatora pra detectores independentes OU aceita os limites"). É lógica do script do motor.

Accepted-limits (R6) — proposto, nunca enterrado sozinho

Vivo, ancorado ao alvo. O loop PROPÕE (plan.proposedLimits); o Planejador adjudica se procede; mas só vira PERMANENTE quando o usuário move pra .claude/qa-loop.config.md. Critério explícito de entrada (é genuinamente espaço-de-input-infinito? ou só difícil?) — não é o lixo do que cansou de consertar; um bug real classificado como limite some pra sempre. Headless propõe + reporta, nunca grava permanente.

Finding de Fase Gate (lint/type/unit/integração vermelho) é INELEGÍVEL a accepted-limit — erro objetivo nunca vira "limite aceito" nem P2 rebaixado. Só findings da Fase Assintótica podem virar limite; o gate é absoluto por definição.

Findings arquiteturais (não viram teste-red)

"A abordagem é frágil", "acoplamento alto", "naming enganoso" não reproduzem por teste determinístico. → excluídos do regression gate; só reportados (viram bucket plan-flaw / alerta, ou P2 documentado). Nunca consertados automaticamente sem rede.


Modo headless (pro /sovai)

Com --headless: o loop NUNCA pergunta nada (auto-classifica domínio como assintótico se ambíguo, usa os defaults, pula o veredito 0-10). Alertas de plan-flaw continuam NÃO virando fix — ficam no relatório pro usuário revisar depois. Headless ≠ licença pra re-planejar nem pra ratificar accepted-limit. O relatório da /qa-loop vira a seção final de QA do report do /sovai.

A Fase Gate (absoluta) vale em headless também — o gate vermelho não pergunta nada, mas entra no relatório como bloqueante (stopReason='gate-red'), virando item de "Bloqueios (precisam de você)" no report do /sovai. Conserto de fundamento (lint/type/unit/integração) está no mandato do headless; re-planejar um plan-flaw, não.

Quando NÃO usar

  • Sem nada implementado pra revisar → recuse.
  • Aplicar fix sem rede de regressão E sem aceitar o teto-1 da Camada 5 → declare a cegueira, não finja.
  • Tarefa puramente convergente de 1 passo que você já sabe consertar → só conserte, não embrulhe num loop.

Regras de segurança

  • Nunca declarar "rodada limpa" sem complete=true (todos os ângulos do checklist cobertos).
  • Nunca declarar SUCESSO com a Fase Gate vermelha. lint/type/unit/integração do repo têm que estar 100% verdes (incluindo pré-existentes); se travar, stopReason='gate-red', nunca "sucesso". Erro objetivo nunca vira accepted-limit nem P2 rebaixado.
  • Fase Gate é repo-inteiro-absoluto, mas com transparência: o relatório separa "conserto da revisão" de "débito pré-existente zerado" (com diff) — conserto repo-wide nunca é silencioso (respeita surgical-changes).
  • Nunca consertar um finding de bucket plan-drift no sentido que afasta do plano; nunca implementar um plan-flaw.
  • Nunca rodar como se houvesse rede de regressão quando não há (Camada 5 é declaração explícita, não fallback silencioso).
  • O teto é trava, não meta — não pare só porque "rodou N vezes" se ainda há severidade real.
  • Quem decide keep/revert de um fix é o script + o Planejador, nunca o agente que fez o fix — mesmo com todo mundo em Opus 5, o gate é separação de papel, não desconfiança de modelo.

Gives 0 of the 12 instructions most e2e browser skills give

Counted across 407 of the 410 authors here whose files we hold, read 2026-08-06

  • use page object model patternin 35 of 407, across 25 files
  • Snapshot to get element refsin 24 of 407, across 14 files
  • keep tests independentin 23 of 407, across 18 files
  • Interact using refs from the latest snapshotin 23 of 407, across 11 files
  • clean up test data after each testin 21 of 407, across 15 files
  • test user behavior not implementationin 20 of 407, across 14 files
  • quarantine flaky tests explicitlyin 19 of 407, across 10 files
  • wait for specific network conditionsin 18 of 407, across 8 files
  • re-snapshot after navigation or dom changesin 17 of 407, across 10 files
  • Detect running dev servers before writing test codein 17 of 407, across 7 files
  • use web-first assertionsin 17 of 407, across 14 files
  • capture screenshots or videos on test failurein 17 of 407, across 14 files

Said here and by no other author read

  • run the review and fix loop via workflow tool
  • stop the loop on diminishing returns
  • anchor the review against the implementation plan
  • classify each finding into implementation plan-drift or plan-flaw
  • fix only implementation findings one at a time
  • materialize accepted-limits for low severity findings

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.