Pular para conteúdo

AUR-539 — Os aceites do AUR-467 e AUR-477 voltam a passar com o prompt atual

Problema

Os aceites selados de dois cards em done falhavam em main, sem nenhuma mudança de comportamento: AUR-467 (oci-run, seletor padrão AC-001) saía 79 — "assembly failed: prompt instructions and fixed content need 3789 tokens and the reply reserves 40, which leaves no room for a single code change in the 1700-token budget" — e AUR-477 (oci-run, seletor padrão all, que inclui E2EAUR477) saía 1 pelo mesmo motivo a 4000 tokens. A falha já existia no commit base 1d26968.

Medição

O conteúdo fixo de um prompt de review (PromptBuilder.BuildPrompt, schema "review": instruções do template, catálogo de regras, métricas, idioma, escopo da mudança, mais o cabeçalho fixo do conteúdo do usuário) foi medido com um diff vazio, chamando o novo PromptBuilder.FixedOverheadTokens (internal/prompt/builder.go):

O que Tokens (heurística ~4 chars/token)
FixedOverheadTokens (schema "review", diff vazio) 3800
FixedOverheadTokens (schema "summary", diff vazio) 61
System (instruções + catálogo) isolado, schema "review" 3749
Catálogo de regras (RenderRuleCatalog(DefaultRuleCatalog)) 262 (1049 chars)

Os orçamentos que os aceites fixavam (1700, 2200 em AUR-467; 800–4000 em AUR-477) ficam abaixo dos ~3800 tokens que o schema "review" sozinho já consome — não há mais margem nenhuma para um único hunk de código, daí a recusa (que é o comportamento correto do AC-003 do AUR-467/477: a montagem recusa alto e bom som em vez de mandar um prompt sem diff).

O schema "summary" (usado pelos seletores unit/integration do AUR-477) tem overhead fixo de 61 tokens — não é afetado pelo crescimento do catálogo de regras/instruções do schema "review", por isso os orçamentos literais desses dois arquivos (tests/unit/AUR-477.go, tests/integration/AUR-477.go) continuam passando sem alteração; só o E2E do AUR-477 (que usa o schema "review" via internal/review.Reviewer) e as fixturas do AUR-467 (que também usam "review") precisavam de orçamento derivado.

Decisão: derivar os orçamentos, não encolher o prompt

Encolher o prompt (instruções, catálogo de regras, schema de resposta) precisaria provar que nenhum comportamento se perde — o catálogo é a lista fechada de rule_id que o gate do AUR-434 aceita, e o schema ensina o formato de resposta exigido pelo parser. Não há medição de inchaço: o conteúdo está onde o AUR-430/AUR-461/AUR-519 o deixaram, crescendo porque mais regras e seções foram adicionadas, não porque algo ficou redundante. A decisão foi derivar o orçamento dos testes do conteúdo fixo medido, nunca de um número que envelhece.

Implementação

  • internal/prompt/builder.go: extraído fixedOverhead (privado, usado por BuildPrompt) e exposto PromptBuilder.FixedOverheadTokens(diff, metrics, opts) (int, error) — mesma computação, uma única fonte de verdade, sem duplicar a lógica entre produto e teste.
  • internal/prompt/overhead_test.go (novo): TestAUR539FixedOverheadRefusalBoundary prova AC-002 (a recusa continua existindo: MaxTokens == FixedOverheadTokens não deixa espaço algum e BuildPrompt recusa, com a mensagem exata) e AC-003 (o mesmo piso medido, com margem, admite o diff).
  • tests/unit/AUR-467.go: as três fixturas que fixavam 1700/40 e 1510/20 agora buscam, a partir de FixedOverheadTokens, o menor orçamento onde a forma exigida pelo teste (algum arquivo revisado e algum omitido; ou hunk0 presente e hunk1 ausente) realmente ocorre — nunca um literal.
  • tests/integration/AUR-467.go: ArithmeticConsistency troca os literais {1700, 2200, 8000} por {fixedOverhead+200, fixedOverhead+700, fixedOverhead+6000}.
  • tests/acceptance/AUR-467.sh: o modo "tight" do programa gerado (cmd/aur467dump) busca o orçamento mínimo via FixedOverheadTokens em vez do literal 1700, 40.
  • tests/e2e/AUR-477.sh: o programa gerado mede FixedOverheadTokens para o diff de 200 arquivos e usa fixedOverhead + 1000 em vez do literal MaxTokens: 4000. A margem de 1000 tokens continua muito menor que o pior caso anterior ao AUR-477 (uma bala por arquivo omitido, ~200 arquivos), então o teste ainda prova a reserva limitada (maxOmittedBullets), não apenas "deu mais orçamento".
  • tests/acceptance/AUR-539.sh (novo): seletores all, AC-001 (os programas de teste do AUR-467 e AUR-477 passam), AC-002 (a recusa continua testada), AC-003 (os literais antigos não reapareceram e FixedOverheadTokens é usado nos arquivos reescritos) e AC-001-MUT-001 (restaurar o orçamento fixo antigo em uma das fixturas do AUR-467 reproduz a recusa 1700 < ~3800 e vira RED).

Critérios de aceite

  • AC-001: oci-run --card AUR-467 (seletor padrão AC-001) e oci-run --card AUR-477 (seletor padrão all, inclui E2EAUR477) saem 0 em main com estes commits.
  • AC-002: a recusa de montar um prompt sem espaço para o diff continua valendo e testada (TestAUR539FixedOverheadRefusalBoundary, testAUR467PartialCoverageDeclared, AC-003 do AUR-477).
  • AC-003: o orçamento usado nos aceites é derivado de FixedOverheadTokens, não de um literal.

Mutação

  • MUT-001: restaurar, em uma cópia staged de tests/unit/AUR-467.go, o orçamento fixo antigo (MaxTokens: 1700) na fixtura de AC-003 em vez do orçamento derivado — a função ainda compila, mas BuildPrompt recusa (3800 > 1700) e o teste cai (RED). Ver tests/acceptance/AUR-539.sh seletor AC-001-MUT-001.

Lacunas conhecidas (fora do escopo deste card)

  • tests/e2e/AUR-467.sh (seletor E2EAUR467) falha em main, sem qualquer mudança deste card, por um motivo não relacionado a orçamento de token (um check de "no-regression-gate" sobre NOTES.txt / config/demo-tokens.txt). Esse arquivo não está nos paths do card, o seletor padrão AC-001 que o oci-run do AUR-467 executa nunca o invoca, e o Non-goal deste card proíbe mexer no que o AUR-467/477 prometem além do orçamento. Fica registrado para um card futuro.
  • Os orçamentos "com espaço de sobra" (8000/1000) que permanecem literais em tests/unit/AUR-467.go (AC-001, AC-002, blocker de medição) e tests/integration/AUR-467.go (três testes de controle) não foram convertidos para FixedOverheadTokens — hoje 8000 ainda está bem acima de ~3800 e essas fixturas não fazem parte da falha que motivou o card. Continuam sendo um literal que pode, no futuro, sofrer o mesmo envelhecimento; registrado como dívida, não corrigido sob o prazo deste card.